diff --git a/.github/workflows/buildcomponents.yaml b/.github/workflows/buildcomponents.yaml new file mode 100644 index 000000000..cb38ff549 --- /dev/null +++ b/.github/workflows/buildcomponents.yaml @@ -0,0 +1,65 @@ +name: BuildComponents + +on: + workflow_dispatch: + inputs: + ocm_push: + type: boolean + description: "Push to OCM Repository" + default: false + +jobs: +# lint-and-test: +# uses: ./.github/workflows/lint_and_test.yaml +# permissions: +# contents: read +# pull-requests: read + components: +# needs: lint-and-test + name: Trigger component build + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + packages: write + repository-projects: read + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Setup Go + uses: actions/setup-go@v3 + with: + go-version-file: '${{ github.workspace }}/go.mod' + + - name: Cache go-build and mod + uses: actions/cache@v3 + with: + path: | + ~/.cache/go-build/ + ~/go/pkg/mod/ + key: go-${{ hashFiles('go.sum') }} + restore-keys: | + go- + + - name: Push OCM Components + if: inputs.ocm_push == true + env: + GITHUBORG: ${{ github.repository_owner }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }} + run: make push + + - name: Build OCM Components + if: inputs.ocm_push == false + env: + GITHUBORG: ${{ github.repository_owner }} + run: make ctf + + - name: Upload OCM Archive + uses: actions/upload-artifact@v3 + with: + name: ocm.ctf + path: gen/ctf \ No newline at end of file diff --git a/Makefile b/Makefile index 038cb3dd5..32fc37977 100644 --- a/Makefile +++ b/Makefile @@ -2,14 +2,19 @@ # # SPDX-License-Identifier: Apache-2.0 +NAME := ocm REPO_ROOT := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) VERSION := $(shell go run pkg/version/generate/release_generate.go print-version) +GITHUBORG ?= open-component-model +OCMREPO ?= ghcr.io/$(GITHUBORG)/ocm 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) -REGISTRY := ghcr.io/mandelsoft/ocm -COMPONENT_CLI_IMAGE_REPOSITORY := $(REGISTRY)/cli +CREDS := $(shell $(REPO_ROOT)/hack/githubcreds.sh) +OCM := go run $(REPO_ROOT)/cmds/ocm $(CREDS) + +GEN := $(REPO_ROOT)/gen SOURCES := $(shell go list -f '{{$$I:=.Dir}}{{range .GoFiles }}{{$$I}}/{{.}} {{end}}' ./... ) GOPATH := $(shell go env GOPATH) @@ -83,4 +88,43 @@ $(LOCALBIN): generate-license: for f in $(shell find . -name "*.go" -o -name "*.sh"); do \ reuse addheader -r --copyright="SAP SE or an SAP affiliate company and Open Component Model contributors." --license="Apache-2.0" $$f --skip-unrecognised; \ - done \ No newline at end of file + done + + +$(GEN)/.exists: + @mkdir -p $(GEN) + @touch $@ + +.PHONY: components +components: $(GEN)/.comps + +$(GEN)/.comps: + @echo Helminstaller; cd components/helminstaller; make ctf + @echo HelmDemo; cd components/helmdemo; make ctf + @echo OCMCLI; cd components/ocmcli; make ctf + touch $@ + +.PHONY: ctf +ctf: $(GEN)/ctf + +$(GEN)/ctf: $(GEN)/.exists components + @rm -rf "$(GEN)"/ctf + $(OCM) transfer cv --type tgz -V $(GEN)/helminstaller/ctf $(GEN)/ctf + $(OCM) transfer cv -V $(GEN)/helmdemo/ctf $(GEN)/ctf + $(OCM) transfer cv -V $(GEN)/ocmcli/ctf $(GEN)/ctf + touch $@ + +.PHONY: push +push: $(GEN)/ctf $(GEN)/.push.$(NAME) + +$(GEN)/.push.$(NAME): $(GEN)/ctf + $(OCM) transfer ctf -f $(GEN)/ctf $(OCMREPO) + @touch $@ + +.PHONY: plain-ctf +plain-ctf: $(GEN) + @rm -rf "$(GEN)"/ctf + $(OCM) transfer cv -V $(GEN)/helminstaller/ctf $(GEN)/ctf + $(OCM) transfer cv -V $(GEN)/helmdemo/ctf $(GEN)/ctf + $(OCM) transfer cv -V $(GEN)/ocmcli/ctf $(GEN)/ctf + diff --git a/VERSION b/VERSION new file mode 100644 index 000000000..d5109100e --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.3.0-dev diff --git a/cmds/helminstaller/README.md b/cmds/helminstaller/README.md new file mode 100644 index 000000000..25912eb5a --- /dev/null +++ b/cmds/helminstaller/README.md @@ -0,0 +1,61 @@ +# Helm Installer for the TOI Framework + +The helm installer provides an executor plugin for the [TOI framework](../../docs/reference/ocm_toi-bootstrapping.md). +It can be used to install a top level helm chart, which might optionally +by composed of a set of sub charts. + +## Component + +The helm installer plugin for the TOI framework if provided +by component `ocm.software/toi/installers/helminstaller`. + +It provides a single `toiExecutor` resource with the +name `toiexecutor`. + +## Configuration + +The executor configuration supports the following field: + +- `chart` *[ResourceReference](../../docs/reference/ocm_toi-bootstrapping.md#resourcereference)* (**required**) The resource containing the top level hemlm chart to work on. + +- `subCharts` *map[string]ResourceReference* (**optional**) A set of resources describing charts used as sub charts for the top level chart. The map key is the name of the folder created for the sub chart. + +- `release` *string* (**optional**) The default release name to use for installation. + +- `namespace` *string* (**optional**) The default namespace to use for the installation. + +- `createNamespace` *boolean* (**optional**) If set to true the namespace will be created. + +- `imageMapping` *[]ImageMapping* list of localization rules for images. + +- `values` **yaml* The default values used for installation. They will be overwritten by the given installation values (at top-level). + +- `kubeConfigName` *string* (**optional** default: `target`) The credential name to lookup for the kubeconfig used to access the target Kubernetes cluster. + +### Image Mappings + +Image meappings describe the injection of OCI image locations, names and versions +taken from the component version info dedicated properties of the installation +values. The hemlm chart MUST take all image locations used in the templates +from dedicated values. This is required to support the transport of +component versions into local repositories environments, which should be used +on the installation site. + +An image mapping consists of resource reference fields to refer to an OCI image resource used to extract the image location plus the following additional fields: + +- `tag` *string* (**optional**) The property of the values used to inject the tag/version of the image. + +- `repository` *string* (**optional**) The property of the values used to inject the base URL (location+repository) of the image. + +- `image` *string* (**optional**) The property of the values used to inject the complete image name. + +At least the `image` attribute or the `tag` and `repositories` attributes must be used to provide a complete image location. + +### Configuring Subcharts + +Subcharts are configured as usual with the values for the parent chart (see https://helm.sh/docs/chart_template_guide/subcharts_and_globals/). + +The key of the subchart is used as top-level values key to add settings for the subchart. +Similar to the parent chart, images used by subcharts must be localized via [image mappings](#image-mappings), also. The subchart values must accept tag, repository and/or image value +fields for used images. They are set ny preceeding the name of the value field by the key +of the subchart. diff --git a/cmds/helminstaller/app/config.go b/cmds/helminstaller/app/config.go index cd38dd379..5aa9cc2d3 100644 --- a/cmds/helminstaller/app/config.go +++ b/cmds/helminstaller/app/config.go @@ -13,13 +13,14 @@ import ( ) type Config struct { - Chart v1.ResourceReference `json:"chart,omitempty"` - Release string `json:"release,omitempty"` - Namespace string `json:"namespace,omitempty"` - CreateNamespace bool `json:"createNamespace,omitempty"` - ImageMapping []ImageMapping `json:"imageMapping"` - Values json.RawMessage `json:"values,omitempty"` - KubeConfigName string `json:"kubeConfigName,omitempty"` + Chart v1.ResourceReference `json:"chart,omitempty"` + SubCharts map[string]v1.ResourceReference `json:"subCharts,omitempty"` + Release string `json:"release,omitempty"` + Namespace string `json:"namespace,omitempty"` + CreateNamespace bool `json:"createNamespace,omitempty"` + ImageMapping []ImageMapping `json:"imageMapping"` + Values json.RawMessage `json:"values,omitempty"` + KubeConfigName string `json:"kubeConfigName,omitempty"` } type ImageMapping = localize.ImageMapping diff --git a/cmds/helminstaller/app/execute.go b/cmds/helminstaller/app/execute.go index 33bd913b4..a7bc452c4 100644 --- a/cmds/helminstaller/app/execute.go +++ b/cmds/helminstaller/app/execute.go @@ -5,21 +5,31 @@ package app import ( + "encoding/json" "fmt" - "os" "strings" - "github.com/mandelsoft/vfs/pkg/osfs" + . "github.com/open-component-model/ocm/pkg/exception" + . "github.com/open-component-model/ocm/pkg/finalizer" + + "github.com/mandelsoft/filepath/pkg/filepath" + "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/contexts/ocm" + "github.com/open-component-model/ocm/pkg/common/compression" + "github.com/open-component-model/ocm/pkg/contexts/oci/ociutils/helm/loader" + 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/errors" "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/support" + utils2 "github.com/open-component-model/ocm/pkg/utils" ) func Merge(values ...map[string]interface{}) map[string]interface{} { @@ -33,53 +43,160 @@ func Merge(values ...map[string]interface{}) map[string]interface{} { return result } -func Execute(d driver.Driver, action string, ctx ocm.Context, octx out.Context, cv ocm.ComponentVersionAccess, cfg *Config, values map[string]interface{}, kubeconfig []byte) error { - if action != "install" && action != "uninstall" { - return errors.ErrNotSupported("action", action) +type Execution struct { + driver driver.Driver + *support.ExecutorOptions + path string + fs vfs.FileSystem +} + +func (e *Execution) outf(msg string, args ...interface{}) { + out.Outf(e.OutputContext, msg, args...) +} + +func (e *Execution) unpackChart(dir string) { + e.outf("Unpacking chart archive to %s...\n", dir) + e.Logger.Debug("unpacking chart archive", "directory", dir) + r := Must1f(R1(e.fs.Open(e.path)), "cannot read downloaded chart archive %q", e.path) + defer r.Close() + + e.Logger.Debug("auto decompress downloaded chart") + reader, _ := Must2f(R2(compression.AutoDecompress(r)), "cannot uncompress downloaded chart archive %q", e.path) + e.Logger.Debug("preparing chart filesystem") + chartfs := Must1f(R1(projectionfs.New(e.fs, dir)), "cannot create projection %q", e.path) + e.Logger.Debug("extracting chart archive", "archive", e.path) + Mustf(utils2.ExtractTarToFs(chartfs, reader), "cannot extract downloaded chart archive %q", e.path) + e.Logger.Debug("lookup chart folder") + entries := Must1f(R1(vfs.ReadDir(e.fs, dir)), "cannot find chart folder in %q", dir) + if len(entries) != 1 { + Throw(fmt.Errorf("expected single chart folder in archive, but found %d folders", len(entries))) } - cfgv, err := cfg.GetValues() - if err != nil { - return err + e.Logger.Debug("found chart folder", "folder", entries[0].Name()) + e.path = filepath.Join(dir, entries[0].Name()) +} + +func (e *Execution) addSubCharts(finalize *Finalizer, subCharts map[string]v1.ResourceReference) { + dir := e.path + ".dir" + Mustf(e.fs.Mkdir(dir, 0o700), "cannot mkdir %q", dir) + finalize.With(Calling1(e.fs.RemoveAll, dir)) + + e.unpackChart(dir) + + // prepare dependencies + e.outf("Preparing dependencies...\n") + chartFile := filepath.Join(e.path, "Chart.yaml") + chartData := Must1f(R1(vfs.ReadFile(e.fs, chartFile)), "cannot read Chart.yaml") + + var chart map[string]interface{} + Mustf(runtime.DefaultYAMLEncoding.Unmarshal(chartData, &chart), "cannot parse Chart.yaml") + deps := []interface{}{} + if d := chart["dependencies"]; d != nil { + if a, ok := d.([]interface{}); ok { + deps = a + } } - values = Merge(cfgv, values) - acc, rcv, err := utils.ResolveResourceReference(cv, cfg.Chart, nil) - if err != nil { - return errors.ErrNotFoundWrap(err, "chart reference", cfg.Chart.String()) + var loop Finalizer + defer loop.Finalize() + + charts := filepath.Join(e.path, "charts") + Mustf(e.fs.Mkdir(charts, 0o700), "cannot mkdir %q", charts) + e.outf("Loading %d sub charts into %s...\n", len(subCharts), charts) + for n, r := range subCharts { + e.outf(" Loading sub chart %q from resource %s@%s\n", n, r, common.VersionedElementKey(e.ComponentVersion)) + acc, rcv := Must2f(R2(utils.ResolveResourceReference(e.ComponentVersion, r, nil)), "chart reference", r.String()) + loop.Close(rcv) + + if acc.Meta().Type != resourcetypes.HELM_CHART { + Throw(errors.Newf("%s: resource type %q required, but found %q", r, resourcetypes.HELM_CHART, acc.Meta().Type)) + } + + _, subpath := Must2f(R2(download.For(e.Context).Download(common.NewPrinter(e.OutputContext.StdOut()), acc, filepath.Join(charts, n), e.fs)), "downloading helm chart %s", r) + + chartObj := Must1f(R1(loader.Load(subpath, e.fs)), "cannot load subchart %q", subpath) + found := false + for _, dep := range deps { + m, ok := dep.(map[string]interface{}) + if ok { + if m["alias"] == n { + e.outf(" found dependency %q for subchart %q\n", n, chartObj.Name()) + m["name"] = chartObj.Name() + found = true + break + } + if m["name"] == chartObj.Name() { + if m["alias"] == nil { + e.outf(" setting alias %q for dependency for subchart %q\n", n, chartObj.Name()) + if n != chartObj.Name() { + m["alias"] = n + } + found = true + } + } + } + } + if !found { + e.outf(" adding dependency %q for subchart %q\n", n, chartObj.Name()) + m := map[string]interface{}{} + m["name"] = chartObj.Name() + if n != chartObj.Name() { + m["alias"] = n + } + deps = append(deps, m) + } + loop.Finalize() + } + + chart["dependencies"] = deps + chartData = Must1f(R1(runtime.DefaultYAMLEncoding.Marshal(chart)), "cannot marshal Chart.yaml") + Mustf(vfs.WriteFile(e.fs, chartFile, chartData, 0o600), "cannot write Chart.yaml") +} + +func (e *Execution) Execute(cfg *Config, values map[string]interface{}, kubeconfig []byte) (err error) { + var finalize Finalizer + defer finalize.CatchException().FinalizeWithErrorPropagation(&err) + + if e.Action != "install" && e.Action != "uninstall" { + return errors.ErrNotSupported("action", e.Action) } - defer rcv.Close() - fmt.Printf("Installing helm chart from resource %s@%s", cfg.Chart, common.VersionedElementKey(cv)) + values = Merge(Must1(cfg.GetValues()), values) + + e.outf("Loading helm chart from resource %s@%s\n", cfg.Chart, common.VersionedElementKey(e.ComponentVersion)) + acc, rcv := Must2f(R2(utils.ResolveResourceReference(e.ComponentVersion, cfg.Chart, nil)), "chart reference", cfg.Chart.String()) + finalize.Close(rcv) + if acc.Meta().Type != resourcetypes.HELM_CHART { return errors.Newf("resource type %q required, but found %q", resourcetypes.HELM_CHART, acc.Meta().Type) } // have to use the OS filesystem here for using the helm library - file, err := os.CreateTemp("", "helm-*") + file := Must1(vfs.TempFile(e.fs, "", "helm-*")) + path := file.Name() + file.Close() + e.fs.Remove(path) + + spec := Must1f(R1(acc.Access()), "getting access specification") + data, err := json.Marshal(spec) if err != nil { return err } + toi.Log.Info("starting download", "path", path, "access", string(data)) - path := file.Name() - file.Close() - os.Remove(path) + _, e.path = Must2f(R2(download.For(e.Context).Download(common.NewPrinter(e.OutputContext.StdOut()), acc, path, e.fs)), "downloading helm chart") - _, path, err = download.For(ctx).Download(common.NewPrinter(octx.StdOut()), acc, path, osfs.New()) - if err != nil { - return errors.Wrapf(err, "downloading helm chart") + finalize.With(Calling1(e.fs.Remove, e.path)) + + if len(cfg.SubCharts) > 0 { + e.addSubCharts(&finalize, cfg.SubCharts) } - defer os.Remove(path) + e.outf("Localizing helm chart...\n") + e.Logger.Debug("Localizing helm chart") for i, v := range cfg.ImageMapping { - acc, rcv, err := utils.ResolveResourceReference(cv, v.ResourceReference, nil) - if err != nil { - return errors.ErrNotFoundWrap(err, "mapping", fmt.Sprintf("%d (%s)", i+1, &v.ResourceReference)) - } + acc, rcv := Must2f(R2(utils.ResolveResourceReference(e.ComponentVersion, v.ResourceReference, nil)), "mapping", fmt.Sprintf("%d (%s)", i+1, &v.ResourceReference)) rcv.Close() - ref, err := utils.GetOCIArtifactRef(ctx, acc) - if err != nil { - return errors.Wrapf(err, "mapping %d: cannot resolve resource %s to an OCI Reference", i+1, v) - } + ref := Must1f(R1(utils.GetOCIArtifactRef(e.Context, acc)), "mapping %d: cannot resolve resource %s to an OCI Reference", i+1, v) ix := strings.Index(ref, ":") if ix < 0 { ix = strings.Index(ref, "@") @@ -90,25 +207,21 @@ func Execute(d driver.Driver, action string, ctx ocm.Context, octx out.Context, repo := ref[:ix] tag := ref[ix+1:] if v.Repository != "" { - err = Set(values, v.Repository, repo) - if err != nil { - return errors.Wrapf(err, "mapping %d: assigning repositry to property %q", v.Repository) - } + e.Logger.Debug("substitute image repository", "ref", ref, "target", v.Repository) + Mustf(Set(values, v.Repository, repo), "mapping %d: assigning repositry to property %q", v.Repository) } if v.Tag != "" { - err = Set(values, v.Tag, tag) - if err != nil { - return errors.Wrapf(err, "mapping %d: assigning tag to property %q", v.Tag) - } + e.Logger.Debug("substitute image tag", "ref", ref, "target", v.Tag) + Mustf(Set(values, v.Tag, tag), "mapping %d: assigning tag to property %q", v.Tag) } if v.Image != "" { - err = Set(values, v.Image, ref) - if err != nil { - return errors.Wrapf(err, "mapping %d: assigning image to property %q", v.Image) - } + e.Logger.Debug("substitute image ref", "ref", ref, "target", v.Image) + Mustf(Set(values, v.Image, ref), "mapping %d: assigning image to property %q", v.Image) } } + e.outf("Installing helm chart...\n") + ns := "default" if cfg.Namespace != "" { ns = cfg.Namespace @@ -120,25 +233,23 @@ func Execute(d driver.Driver, action string, ctx ocm.Context, octx out.Context, if s, ok := values["release"].(string); ok && s != "" { release = s } - valuesdata, err := runtime.DefaultYAMLEncoding.Marshal(values) - if err != nil { - return errors.Wrapf(err, "marshal values") - } + e.Logger.Debug("executing helm deployment", "action", e.Action, "namespace", ns, "release", release) + valuesdata := Must1f(R1(runtime.DefaultYAMLEncoding.Marshal(values)), "marshal values") dcfg := &driver.Config{ - ChartPath: path, + ChartPath: e.path, Release: release, Namespace: ns, CreateNamespace: cfg.CreateNamespace, Values: valuesdata, Kubeconfig: kubeconfig, } - switch action { + switch e.Action { case "install": - return d.Install(dcfg) + return e.driver.Install(dcfg) case "uninstall": - return d.Uninstall(dcfg) + return e.driver.Uninstall(dcfg) default: - return errors.ErrNotImplemented("action", action) + return errors.ErrNotImplemented("action", e.Action) } } diff --git a/cmds/helminstaller/app/executor.go b/cmds/helminstaller/app/executor.go index ba0b142ee..f585b72a8 100644 --- a/cmds/helminstaller/app/executor.go +++ b/cmds/helminstaller/app/executor.go @@ -5,6 +5,8 @@ package app import ( + "github.com/mandelsoft/vfs/pkg/osfs" + "github.com/open-component-model/ocm/cmds/helminstaller/app/driver" "github.com/open-component-model/ocm/pkg/errors" "github.com/open-component-model/ocm/pkg/runtime" @@ -39,5 +41,11 @@ func Executor(d driver.Driver, o *support.ExecutorOptions) error { if v == "" { return errors.Wrapf(err, "property KUBECONFIG missing in credential %q", cfg.KubeConfigName) } - return Execute(d, o.Action, o.Context, o.OutputContext, o.ComponentVersion, &cfg, values, []byte(v)) + exec := &Execution{ + driver: d, + ExecutorOptions: o, + path: "", + fs: osfs.New(), + } + return exec.Execute(&cfg, values, []byte(v)) } diff --git a/cmds/ocm/app/app.go b/cmds/ocm/app/app.go index a969a02f5..5860ed9cb 100644 --- a/cmds/ocm/app/app.go +++ b/cmds/ocm/app/app.go @@ -9,19 +9,15 @@ package app import ( "strings" - "github.com/open-component-model/ocm/cmds/ocm/commands/misccmds/action" _ "github.com/open-component-model/ocm/pkg/contexts/clictx/config" _ "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs" - "github.com/mandelsoft/logging" - "github.com/mandelsoft/logging/config" - "github.com/mandelsoft/logging/logrusr" - "github.com/mandelsoft/vfs/pkg/vfs" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/open-component-model/ocm/cmds/ocm/commands/cachecmds" + "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" @@ -32,7 +28,6 @@ import ( "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/sources" - "github.com/open-component-model/ocm/cmds/ocm/commands/toicmds" "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/clean" @@ -51,11 +46,13 @@ import ( 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" + 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" 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/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/clictx" "github.com/open-component-model/ocm/pkg/contexts/credentials" @@ -65,7 +62,6 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/ocm/registration" "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" "github.com/open-component-model/ocm/pkg/errors" - ocmlog "github.com/open-component-model/ocm/pkg/logging" "github.com/open-component-model/ocm/pkg/out" "github.com/open-component-model/ocm/pkg/version" ) @@ -77,12 +73,9 @@ type CLIOptions struct { Context clictx.Context Settings []string Verbose bool - LogLevel string - LogFile string - LogConfig string - Version bool + LogOpts logopts.Options - logFile vfs.File + Version bool } var desc = ` @@ -134,7 +127,7 @@ form
-X <attribute>=<value>
- +` + logopts.Description + ` The value can be a simple type or a json string for complex values. The following attributes are supported: ` + attributes.Attributes() @@ -214,7 +207,7 @@ func newCliCommand(opts *CLIOptions, mod ...func(clictx.Context, *cobra.Command) cmd.AddCommand(cmdutils.HideCommand(cachecmds.NewCommand(opts.Context))) cmd.AddCommand(cmdutils.HideCommand(ocicmds.NewCommand(opts.Context))) cmd.AddCommand(cmdutils.HideCommand(ocmcmds.NewCommand(opts.Context))) - cmd.AddCommand(cmdutils.HideCommand(toicmds.NewCommand(opts.Context))) + //cmd.AddCommand(cmdutils.HideCommand(toicmds.NewCommand(opts.Context))) cmd.AddCommand(cmdutils.HideCommand(creds.NewCommand(opts.Context))) @@ -229,6 +222,7 @@ func newCliCommand(opts *CLIOptions, mod ...func(clictx.Context, *cobra.Command) } // help.Use="help " help.DisableFlagsInUseLine = true + cmd.AddCommand(topiclogging.New(ctx)) cmd.AddCommand(topicconfig.New(ctx)) cmd.AddCommand(topicocirefs.New(ctx)) cmd.AddCommand(topicocmrefs.New(ctx)) @@ -254,18 +248,14 @@ func (o *CLIOptions) AddFlags(fs *pflag.FlagSet) { fs.StringVarP(&o.Config, "config", "", "", "configuration file") fs.StringArrayVarP(&o.Credentials, "cred", "C", nil, "credential setting") fs.StringArrayVarP(&o.Settings, "attribute", "X", nil, "attribute setting") - fs.BoolVarP(&o.Verbose, "verbose", "v", false, "enable verbose logging") - fs.StringVarP(&o.LogLevel, "loglevel", "l", "", "set log level") - fs.StringVarP(&o.LogFile, "logfile", "L", "", "set log file") - fs.StringVarP(&o.LogConfig, "logconfig", "", "", "log config") + fs.BoolVarP(&o.Verbose, "verbose", "v", false, "deprecated: enable logrus verbose logging") fs.BoolVarP(&o.Version, "version", "", false, "show version") // otherwise it is implicitly added by cobra + + o.LogOpts.AddFlags(fs) } func (o *CLIOptions) Close() error { - if o.logFile == nil { - return nil - } - return o.logFile.Close() + return o.LogOpts.Close() } func (o *CLIOptions) Complete() error { @@ -274,42 +264,14 @@ func (o *CLIOptions) Complete() error { } o.Completed = true - var err error if o.Verbose { logrus.SetLevel(logrus.DebugLevel) } - if o.LogLevel != "" { - l, err := logging.ParseLevel(o.LogLevel) - if err != nil { - return errors.Wrapf(err, "invalid log level %q", o.LogLevel) - } - ocmlog.Context().SetDefaultLevel(l) - } else { - ocmlog.Context().SetDefaultLevel(logging.ErrorLevel) - } - - if o.LogFile != "" { - o.logFile, err = o.Context.FileSystem().OpenFile(o.LogFile, vfs.O_CREATE|vfs.O_WRONLY, 0o600) - if err != nil { - return errors.Wrapf(err, "cannot open log file %q", o.LogFile) - } - log := logrus.New() - log.SetFormatter(&logrus.JSONFormatter{TimestampFormat: "2006-01-02 15:04:05"}) - log.SetOutput(o.logFile) - ocmlog.Context().SetBaseLogger(logrusr.New(log)) - } - - if o.LogConfig != "" { - cfg, err := vfs.ReadFile(o.Context.FileSystem(), o.LogConfig) - if err != nil { - return errors.Wrapf(err, "cannot read logging config %q", o.LogFile) - } - if err = config.ConfigureWithData(ocmlog.Context(), cfg); err != nil { - return errors.Wrapf(err, "invalid logging config: %q", o.LogFile) - } + err := o.LogOpts.Configure(o.Context.OCMContext(), nil) + if err != nil { + return err } - _, err = utils.Configure(o.Context.OCMContext(), o.Config, vfsattr.Get(o.Context)) if err != nil { return err diff --git a/cmds/ocm/commands/common/elements/components/cmd.go b/cmds/ocm/commands/common/elements/components/cmd.go index 0dacac82f..307d4f8db 100644 --- a/cmds/ocm/commands/common/elements/components/cmd.go +++ b/cmds/ocm/commands/common/elements/components/cmd.go @@ -9,7 +9,7 @@ import ( 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/components" + 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" ) diff --git a/cmds/ocm/commands/common/options/destoption/option.go b/cmds/ocm/commands/common/options/destoption/option.go index 5b2e9b6fe..45e0f8237 100644 --- a/cmds/ocm/commands/common/options/destoption/option.go +++ b/cmds/ocm/commands/common/options/destoption/option.go @@ -31,7 +31,7 @@ func (d *Option) AddFlags(fs *pflag.FlagSet) { fs.StringVarP(&d.Destination, "outfile", "O", "", "output file or directory") } -func (o *Option) Complete(ctx clictx.Context) error { +func (o *Option) Configure(ctx clictx.Context) error { o.PathFilesystem = ctx.FileSystem() return nil } diff --git a/cmds/ocm/commands/common/options/formatoption/option.go b/cmds/ocm/commands/common/options/formatoption/option.go index 1d6d8d502..78ef396b8 100644 --- a/cmds/ocm/commands/common/options/formatoption/option.go +++ b/cmds/ocm/commands/common/options/formatoption/option.go @@ -52,7 +52,7 @@ func (o *Option) AddFlags(fs *pflag.FlagSet) { fs.StringVarP(&o.format, "type", "t", string(o.Default), fmt.Sprintf("archive format (%s)", strings.Join(o.List, ", "))) } -func (o *Option) Complete(ctx clictx.Context) error { +func (o *Option) Configure(ctx clictx.Context) error { o.Format = accessio.FileFormat(o.format) for _, f := range o.List { if f == string(o.Format) { diff --git a/cmds/ocm/commands/ocicmds/common/options/repooption/option.go b/cmds/ocm/commands/ocicmds/common/options/repooption/option.go index 28d51dd2d..1d2d4866d 100644 --- a/cmds/ocm/commands/ocicmds/common/options/repooption/option.go +++ b/cmds/ocm/commands/ocicmds/common/options/repooption/option.go @@ -32,7 +32,7 @@ func (o *Option) AddFlags(fs *pflag.FlagSet) { fs.StringVarP(&o.Spec, "repo", "", "", "repository name or spec") } -func (o *Option) Complete(ctx clictx.Context) error { +func (o *Option) Configure(ctx clictx.Context) error { return nil } diff --git a/cmds/ocm/commands/ocicmds/common/utils.go b/cmds/ocm/commands/ocicmds/common/utils.go index 91f833b3c..98deef18e 100644 --- a/cmds/ocm/commands/ocicmds/common/utils.go +++ b/cmds/ocm/commands/ocicmds/common/utils.go @@ -20,7 +20,7 @@ func CompleteOptionsWithContext(ctx clictx.Context, session oci.Session) options return c.CompleteWithSession(ctx.OCI(), session) } if c, ok := opt.(options.OptionWithCLIContextCompleter); ok { - return c.Complete(ctx) + return c.Configure(ctx) } if c, ok := opt.(options.SimpleOptionCompleter); ok { return c.Complete() diff --git a/cmds/ocm/commands/ocicmds/ctf/create/cmd.go b/cmds/ocm/commands/ocicmds/ctf/create/cmd.go index b13bab66f..5c9ddb65f 100644 --- a/cmds/ocm/commands/ocicmds/ctf/create/cmd.go +++ b/cmds/ocm/commands/ocicmds/ctf/create/cmd.go @@ -57,7 +57,7 @@ func (o *Command) AddFlags(fs *pflag.FlagSet) { } func (o *Command) Complete(args []string) error { - err := o.Format.Complete(o.Context) + err := o.Format.Configure(o.Context) if err != nil { return err } diff --git a/cmds/ocm/commands/ocmcmds/common/addhdlrs/comp/components.go b/cmds/ocm/commands/ocmcmds/common/addhdlrs/comp/components.go index 94013dcb2..5d0bb393c 100644 --- a/cmds/ocm/commands/ocmcmds/common/addhdlrs/comp/components.go +++ b/cmds/ocm/commands/ocmcmds/common/addhdlrs/comp/components.go @@ -7,17 +7,71 @@ package comp import ( "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/errors" + "github.com/open-component-model/ocm/pkg/finalizer" + "github.com/open-component-model/ocm/pkg/generics" + "github.com/open-component-model/ocm/pkg/out" ) -func ProcessComponents(ctx clictx.Context, ictx inputs.Context, repo ocm.Repository, h *ResourceSpecHandler, elems []addhdlrs.Element) error { +func ProcessComponents(ctx clictx.Context, ictx inputs.Context, repo ocm.Repository, complete ocm.ComponentVersionResolver, thdlr transferhandler.TransferHandler, h *ResourceSpecHandler, elems []addhdlrs.Element) error { + index := generics.Set[common.NameVersion]{} for _, elem := range elems { + if r, ok := elem.Spec().(*ResourceSpec); ok { + index.Add(common.NewNameVersion(r.Name, r.Version)) + } + } + + var finalize finalizer.Finalizer + defer finalize.Finalize() + + for _, elem := range elems { + loop := finalize.Nested() err := h.Add(ctx, ictx.Section("adding %s...", elem.Spec().Info()), elem, repo) if err != nil { return errors.Wrapf(err, "failed adding component %q(%s)", elem.Spec().GetName(), elem.Source()) } + + if r, ok := elem.Spec().(*ResourceSpec); complete != nil && thdlr != nil && ok { + cv, err := repo.LookupComponentVersion(r.Name, r.Version) + if err != nil { + return errors.Wrapf(err, "accessing added component version failed") + } + loop.Close(cv) + if len(cv.GetDescriptor().References) > 0 { + ictx.Printf("completing %s:%s...\n", r.Name, r.Version) + for _, cr := range cv.GetDescriptor().References { + loop := loop.Nested() + nv := common.NewNameVersion(cr.ComponentName, cr.Version) + if index.Contains(nv) { + continue + } + found, err := repo.LookupComponentVersion(nv.GetName(), nv.GetVersion()) + if err == nil && found != nil { + found.Close() + out.Outf(ctx, " reference %s[%s] already found\n", cr.Name, nv) + continue + } + found, err = complete.LookupComponentVersion(nv.GetName(), nv.GetVersion()) + if err != nil || found == nil { + return errors.NewEf(err, "referenced component version %s not found", nv) + } + loop.Close(found) + err = transfer.TransferVersion(ictx.Printer().AddGap(" "), nil, found, repo, thdlr) + if err != nil { + return errors.Wrapf(err, "completing reference %s[%s] of %s:%s failed", cr.Name, nv, r.Name, r.Version) + } + err = loop.Finalize() + if err != nil { + return err + } + } + } + } } return nil } diff --git a/cmds/ocm/commands/ocmcmds/common/addhdlrs/comp/elements.go b/cmds/ocm/commands/ocmcmds/common/addhdlrs/comp/elements.go index 1fef325ba..add8ec9d5 100644 --- a/cmds/ocm/commands/ocmcmds/common/addhdlrs/comp/elements.go +++ b/cmds/ocm/commands/ocmcmds/common/addhdlrs/comp/elements.go @@ -7,6 +7,8 @@ package comp import ( "fmt" + . "github.com/open-component-model/ocm/pkg/finalizer" + "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" @@ -20,7 +22,6 @@ import ( metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" "github.com/open-component-model/ocm/pkg/errors" "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" ) const ( @@ -62,7 +63,7 @@ func (*ResourceSpecHandler) Set(v ocm.ComponentVersionAccess, r addhdlrs.Element } func (*ResourceSpecHandler) Add(ctx clictx.Context, ictx inputs.Context, elem addhdlrs.Element, repo ocm.Repository) (err error) { - var final utils.Finalizer + var final Finalizer defer final.FinalizeWithErrorPropagation(&err) r, ok := elem.Spec().(*ResourceSpec) diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/inputtype.go b/cmds/ocm/commands/ocmcmds/common/inputs/inputtype.go index fc4056b0f..2638872e9 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/inputtype.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/inputtype.go @@ -29,6 +29,7 @@ const KIND_INPUTTYPE = "input type" type Context interface { clictx.Context + Printer() common.Printer Printf(msg string, args ...interface{}) (int, error) Variables() map[string]interface{} Section(msg string, args ...interface{}) Context @@ -74,10 +75,19 @@ func (c *context) AddGap(gap string) Context { } } +type InputResourceInfo struct { + // ComponentVersion is the name of the component version to generate. + ComponentVersion common.NameVersion + // ElementName is the name of the element to create. + ElementName string + // The path of the file the inputs description has been taken from. + InputFilePath string +} + type InputSpec interface { runtime.VersionedTypedObject Validate(fldPath *field.Path, ctx Context, inputFilePath string) field.ErrorList - GetBlob(ctx Context, nv common.NameVersion, inputFilePath string) (accessio.TemporaryBlobAccess, string, error) + GetBlob(ctx Context, info InputResourceInfo) (accessio.TemporaryBlobAccess, string, error) } type InputType interface { @@ -269,7 +279,7 @@ func (r *UnknownInputSpec) Validate(fldPath *field.Path, ctx Context, inputFileP return field.ErrorList{field.Invalid(fldPath.Child("type"), r.GetType(), "unknown type")} } -func (r *UnknownInputSpec) GetBlob(ctx Context, nv common.NameVersion, inputFilePath string) (accessio.TemporaryBlobAccess, string, error) { +func (r *UnknownInputSpec) GetBlob(ctx Context, info InputResourceInfo) (accessio.TemporaryBlobAccess, string, error) { return nil, "", errors.ErrUnknown("input type", r.GetType()) } @@ -322,7 +332,7 @@ func (s *GenericInputSpec) Validate(fldPath *field.Path, ctx Context, inputFileP return s.effective.Validate(fldPath, ctx, inputFilePath) } -func (s *GenericInputSpec) GetBlob(ctx Context, nv common.NameVersion, inputFilePath string) (accessio.TemporaryBlobAccess, string, error) { +func (s *GenericInputSpec) GetBlob(ctx Context, info InputResourceInfo) (accessio.TemporaryBlobAccess, string, error) { if s.effective == nil { var err error s.effective, err = s.Evaluate(For(ctx)) @@ -330,7 +340,7 @@ func (s *GenericInputSpec) GetBlob(ctx Context, nv common.NameVersion, inputFile return nil, "", err } } - return s.effective.GetBlob(ctx, nv, inputFilePath) + return s.effective.GetBlob(ctx, info) } func (s *GenericInputSpec) Evaluate(scheme InputTypeScheme) (InputSpec, error) { 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 7998f4da9..ac3c38a74 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/binary/spec.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/binary/spec.go @@ -9,7 +9,6 @@ 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" - "github.com/open-component-model/ocm/pkg/common" "github.com/open-component-model/ocm/pkg/common/accessio" "github.com/open-component-model/ocm/pkg/runtime" ) @@ -38,6 +37,6 @@ func (s *Spec) Validate(fldPath *field.Path, ctx inputs.Context, inputFilePath s return nil } -func (s *Spec) GetBlob(ctx inputs.Context, nv common.NameVersion, inputFilePath string) (accessio.TemporaryBlobAccess, string, error) { +func (s *Spec) GetBlob(ctx inputs.Context, info inputs.InputResourceInfo) (accessio.TemporaryBlobAccess, string, error) { return s.ProcessBlob(ctx, accessio.DataAccessForBytes([]byte(s.Data)), ctx.FileSystem()) } 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 f91dbbbf3..461819594 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/directory/spec.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/directory/spec.go @@ -12,7 +12,6 @@ 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" - "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" @@ -60,11 +59,11 @@ func (s *Spec) Validate(fldPath *field.Path, ctx inputs.Context, inputFilePath s return allErrs } -func (s *Spec) GetBlob(ctx inputs.Context, nv common.NameVersion, inputFilePath string) (accessio.TemporaryBlobAccess, string, error) { +func (s *Spec) GetBlob(ctx inputs.Context, info inputs.InputResourceInfo) (accessio.TemporaryBlobAccess, string, error) { fs := ctx.FileSystem() - inputInfo, inputPath, err := inputs.FileInfo(ctx, s.Path, inputFilePath) + inputInfo, inputPath, err := inputs.FileInfo(ctx, s.Path, info.InputFilePath) if err != nil { - return nil, "", fmt.Errorf("resource dir %s: %w", inputFilePath, err) + return nil, "", fmt.Errorf("resource dir %s: %w", info.InputFilePath, err) } if !inputInfo.IsDir() { return nil, "", fmt.Errorf("resource type is dir but a file was provided") 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 6458ad94d..72fec5c53 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/docker/spec.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/docker/spec.go @@ -10,7 +10,6 @@ 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" "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/ociimage" - "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/repositories/artifactset" "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/docker" @@ -46,7 +45,7 @@ func (s *Spec) Validate(fldPath *field.Path, ctx inputs.Context, inputFilePath s return allErrs } -func (s *Spec) GetBlob(ctx inputs.Context, nv common.NameVersion, inputFilePath string) (accessio.TemporaryBlobAccess, string, error) { +func (s *Spec) GetBlob(ctx inputs.Context, info inputs.InputResourceInfo) (accessio.TemporaryBlobAccess, string, error) { ctx.Printf("image %s\n", s.Path) locator, version, err := docker.ParseGenericRef(s.Path) if err != nil { @@ -63,11 +62,11 @@ func (s *Spec) GetBlob(ctx inputs.Context, nv common.NameVersion, inputFilePath } if version == "" || version == "latest" { - version = nv.GetVersion() + version = info.ComponentVersion.GetVersion() } blob, err := artifactset.SynthesizeArtifactBlob(ns, version) if err != nil { return nil, "", err } - return blob, ociartifact.Hint(nv, locator, s.Repository, version), nil + return blob, ociartifact.Hint(info.ComponentVersion, locator, s.Repository, version), nil } 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 474ab0363..8402c12ac 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/dockermulti/spec.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/dockermulti/spec.go @@ -7,12 +7,13 @@ package dockermulti import ( "fmt" + . "github.com/open-component-model/ocm/pkg/finalizer" + "github.com/opencontainers/go-digest" "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/types/ociimage" - "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" @@ -23,7 +24,6 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" "github.com/open-component-model/ocm/pkg/errors" "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" ) type Spec struct { @@ -68,7 +68,7 @@ func (s *Spec) Validate(fldPath *field.Path, ctx inputs.Context, inputFilePath s return allErrs } -func (s *Spec) getVariant(ctx clictx.Context, finalize *utils.Finalizer, variant string) (oci.ArtifactAccess, error) { +func (s *Spec) getVariant(ctx clictx.Context, finalize *Finalizer, variant string) (oci.ArtifactAccess, error) { locator, version, err := docker.ParseGenericRef(variant) if err != nil { return nil, err @@ -96,7 +96,7 @@ func (s *Spec) getVariant(ctx clictx.Context, finalize *utils.Finalizer, variant return art, nil } -func (s *Spec) GetBlob(ctx inputs.Context, nv common.NameVersion, inputFilePath string) (accessio.TemporaryBlobAccess, string, error) { +func (s *Spec) GetBlob(ctx inputs.Context, info inputs.InputResourceInfo) (accessio.TemporaryBlobAccess, string, error) { index := artdesc.NewIndexArtifact() i := 0 @@ -123,7 +123,7 @@ func (s *Spec) GetBlob(ctx inputs.Context, nv common.NameVersion, inputFilePath return nil } - blob, err := artifactset.SynthesizeArtifactBlobFor(nv.GetVersion(), func() (fac artifactset.ArtifactFactory, main bool, err error) { + blob, err := artifactset.SynthesizeArtifactBlobFor(info.ComponentVersion.GetVersion(), func() (fac artifactset.ArtifactFactory, main bool, err error) { var art cpi.ArtifactAccess var blob accessio.BlobAccess @@ -150,7 +150,7 @@ func (s *Spec) GetBlob(ctx inputs.Context, nv common.NameVersion, inputFilePath default: // provide variant ctx.Printf("image %d: %s\n", i, s.Variants[i]) - var finalize utils.Finalizer + var finalize Finalizer art, err = s.getVariant(ctx, &finalize, s.Variants[i]) @@ -168,5 +168,5 @@ func (s *Spec) GetBlob(ctx inputs.Context, nv common.NameVersion, inputFilePath if err != nil { return nil, "", err } - return blob, ociartifact.Hint(nv, nv.GetName(), s.Repository, nv.GetVersion()), nil + return blob, ociartifact.Hint(info.ComponentVersion, info.ElementName, s.Repository, info.ComponentVersion.GetVersion()), nil } 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 6e62ec99a..bc373d205 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/file/spec.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/file/spec.go @@ -9,7 +9,6 @@ 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" - "github.com/open-component-model/ocm/pkg/common" "github.com/open-component-model/ocm/pkg/common/accessio" ) @@ -29,6 +28,6 @@ func (s *Spec) Validate(fldPath *field.Path, ctx inputs.Context, inputFilePath s return (&FileProcessSpec{s.MediaFileSpec, nil}).Validate(fldPath, ctx, inputFilePath) } -func (s *Spec) GetBlob(ctx inputs.Context, nv common.NameVersion, inputFilePath string) (accessio.TemporaryBlobAccess, string, error) { - return (&FileProcessSpec{s.MediaFileSpec, nil}).GetBlob(ctx, nv, inputFilePath) +func (s *Spec) GetBlob(ctx inputs.Context, info inputs.InputResourceInfo) (accessio.TemporaryBlobAccess, string, error) { + return (&FileProcessSpec{s.MediaFileSpec, nil}).GetBlob(ctx, info) } 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 7ada6952b..6148fe2fd 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/file/support.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/file/support.go @@ -14,7 +14,6 @@ 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" - "github.com/open-component-model/ocm/pkg/common" "github.com/open-component-model/ocm/pkg/common/accessio" "github.com/open-component-model/ocm/pkg/errors" "github.com/open-component-model/ocm/pkg/mime" @@ -36,9 +35,9 @@ func (s *FileProcessSpec) Validate(fldPath *field.Path, ctx inputs.Context, inpu return allErrs } -func (s *FileProcessSpec) GetBlob(ctx inputs.Context, nv common.NameVersion, inputFilePath string) (accessio.TemporaryBlobAccess, string, error) { +func (s *FileProcessSpec) GetBlob(ctx inputs.Context, info inputs.InputResourceInfo) (accessio.TemporaryBlobAccess, string, error) { fs := ctx.FileSystem() - inputInfo, inputPath, err := inputs.FileInfo(ctx, s.Path, inputFilePath) + inputInfo, inputPath, err := inputs.FileInfo(ctx, s.Path, info.InputFilePath) if err != nil { return nil, "", err } @@ -60,7 +59,7 @@ func (s *FileProcessSpec) GetBlob(ctx inputs.Context, nv common.NameVersion, inp if err != nil { return nil, "", errors.Wrapf(err, "cannot read input file %s", inputPath) } - dir, err := inputs.GetBaseDir(ctx.FileSystem(), inputFilePath) + dir, err := inputs.GetBaseDir(ctx.FileSystem(), info.InputFilePath) if err != nil { return nil, "", err } 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 62ceb405e..bc0fa34f2 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/helm/spec.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/helm/spec.go @@ -9,7 +9,6 @@ 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" - "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/ociutils/helm" "github.com/open-component-model/ocm/pkg/contexts/oci/ociutils/helm/loader" @@ -45,8 +44,8 @@ func (s *Spec) Validate(fldPath *field.Path, ctx inputs.Context, inputFilePath s return allErrs } -func (s *Spec) GetBlob(ctx inputs.Context, nv common.NameVersion, inputFilePath string) (accessio.TemporaryBlobAccess, string, error) { - _, inputPath, err := inputs.FileInfo(ctx, s.Path, inputFilePath) +func (s *Spec) GetBlob(ctx inputs.Context, info inputs.InputResourceInfo) (accessio.TemporaryBlobAccess, string, error) { + _, inputPath, err := inputs.FileInfo(ctx, s.Path, info.InputFilePath) if err != nil { return nil, "", err } @@ -59,12 +58,12 @@ func (s *Spec) GetBlob(ctx inputs.Context, nv common.NameVersion, inputFilePath vers = s.Version } if vers == "" { - vers = nv.GetVersion() + vers = info.ComponentVersion.GetVersion() } blob, err := helm.SynthesizeArtifactBlob(inputPath, ctx.FileSystem()) if err != nil { return nil, "", err } - return blob, ociartifact.Hint(nv, chart.Name(), s.Repository, vers), err + return blob, ociartifact.Hint(info.ComponentVersion, chart.Name(), s.Repository, vers), err } diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/ociimage/spec.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/ociimage/spec.go index be4a86c2f..bd16f45ec 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/ociimage/spec.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/ociimage/spec.go @@ -11,7 +11,6 @@ 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" - "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" "github.com/open-component-model/ocm/pkg/contexts/oci/grammar" @@ -48,7 +47,7 @@ func (s *Spec) Validate(fldPath *field.Path, ctx inputs.Context, inputFilePath s return allErrs } -func (s *Spec) GetBlob(ctx inputs.Context, nv common.NameVersion, inputFilePath string) (accessio.TemporaryBlobAccess, string, error) { +func (s *Spec) GetBlob(ctx inputs.Context, info inputs.InputResourceInfo) (accessio.TemporaryBlobAccess, string, error) { ctx.Printf("image %s\n", s.Path) ref, err := oci.ParseRef(s.Path) if err != nil { @@ -71,13 +70,13 @@ func (s *Spec) GetBlob(ctx inputs.Context, nv common.NameVersion, inputFilePath version := ref.Version() if version == "" || version == "latest" { - version = nv.GetVersion() + version = info.ComponentVersion.GetVersion() } blob, err := artifactset.SynthesizeArtifactBlob(ns, version) if err != nil { return nil, "", err } - return blob, ociartifact.Hint(nv, ref.Repository, s.Repository, version), nil + return blob, ociartifact.Hint(info.ComponentVersion, info.ElementName, s.Repository, version), nil } func ValidateRepository(fldPath *field.Path, allErrs field.ErrorList, repo string) field.ErrorList { 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 7ff0d562c..b2ddd8696 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/spiff/spec.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/spiff/spec.go @@ -15,7 +15,6 @@ 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" "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/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/errors" "github.com/open-component-model/ocm/pkg/runtime" @@ -63,8 +62,8 @@ func (s *Spec) Validate(fldPath *field.Path, ctx inputs.Context, inputFilePath s return allErrs } -func (s *Spec) GetBlob(ctx inputs.Context, nv common.NameVersion, inputFilePath string) (accessio.TemporaryBlobAccess, string, error) { - return (&file.FileProcessSpec{s.MediaFileSpec, s.process}).GetBlob(ctx, nv, inputFilePath) +func (s *Spec) GetBlob(ctx inputs.Context, info inputs.InputResourceInfo) (accessio.TemporaryBlobAccess, string, error) { + return (&file.FileProcessSpec{s.MediaFileSpec, s.process}).GetBlob(ctx, info) } func (s *Spec) process(ctx inputs.Context, inputFilePath string, data []byte) ([]byte, error) { 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 1ce76422e..90d2da74b 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 @@ -7,6 +7,7 @@ package spiff_test 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/cmds/ocm/commands/ocmcmds/common/inputs" @@ -17,9 +18,16 @@ import ( var _ = Describe("spiff processing", func() { var env *TestEnv var ictx inputs.Context + var info inputs.InputResourceInfo nv := common.NewNameVersion("test", "v1") + BeforeEach(func() { + info = inputs.InputResourceInfo{ + ComponentVersion: nv, + ElementName: "elemname", + InputFilePath: "/testdata/dummy", + } env = NewTestEnv(TestData()) ictx = inputs.NewContext(env.Context, common.NewPrinter(env.Context.StdOut()), nil) }) @@ -31,7 +39,7 @@ var _ = Describe("spiff processing", func() { It("processes template", func() { spec, err := spiff.New("test1.yaml", "", false, nil) Expect(err).To(Succeed()) - blob, s, err := spec.GetBlob(ictx, nv, "/testdata/dummy") + blob, s, err := spec.GetBlob(ictx, info) Expect(err).To(Succeed()) Expect(s).To(Equal("")) data, err := blob.Get() @@ -44,7 +52,7 @@ bob: 25 It("processes template with values", func() { spec, err := spiff.New("test1.yaml", "", false, map[string]interface{}{"diff": 2}) Expect(err).To(Succeed()) - blob, s, err := spec.GetBlob(ictx, nv, "/testdata/dummy") + blob, s, err := spec.GetBlob(ictx, info) Expect(err).To(Succeed()) Expect(s).To(Equal("")) data, err := blob.Get() @@ -57,7 +65,8 @@ bob: 26 It("processes template with values with local working directory", func() { spec, err := spiff.New("test.yaml", "", false, map[string]interface{}{"diff": 2}) Expect(err).To(Succeed()) - blob, s, err := spec.GetBlob(ictx, nv, "/testdata/subdir/dummy") + info.InputFilePath = "/testdata/subdir/dummy" + blob, s, err := spec.GetBlob(ictx, info) Expect(err).To(Succeed()) Expect(s).To(Equal("")) data, err := blob.Get() 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 17fddd693..faabaac58 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/utf8/spec.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/utf8/spec.go @@ -9,7 +9,6 @@ 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" - "github.com/open-component-model/ocm/pkg/common" "github.com/open-component-model/ocm/pkg/common/accessio" "github.com/open-component-model/ocm/pkg/runtime" ) @@ -38,6 +37,6 @@ func (s *Spec) Validate(fldPath *field.Path, ctx inputs.Context, inputFilePath s return nil } -func (s *Spec) GetBlob(ctx inputs.Context, nv common.NameVersion, inputFilePath string) (accessio.TemporaryBlobAccess, string, error) { +func (s *Spec) GetBlob(ctx inputs.Context, info inputs.InputResourceInfo) (accessio.TemporaryBlobAccess, string, error) { return s.ProcessBlob(ctx, accessio.DataAccessForBytes([]byte(s.Text)), ctx.FileSystem()) } diff --git a/cmds/ocm/commands/ocmcmds/common/options/hashoption/option.go b/cmds/ocm/commands/ocmcmds/common/options/hashoption/option.go index 83c2a5945..40ed7f25a 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/hashoption/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/hashoption/option.go @@ -42,7 +42,7 @@ func (o *Option) AddFlags(fs *pflag.FlagSet) { fs.StringVarP(&o.hashAlgorithm, "hash", "H", sha256.Algorithm, "hash algorithm") } -func (o *Option) Complete(ctx clictx.Context) error { +func (o *Option) Configure(ctx clictx.Context) error { if o.NormAlgorithm == "" { o.NormAlgorithm = jsonv1.Algorithm } diff --git a/cmds/ocm/commands/ocmcmds/common/options/keepglobaloption/option.go b/cmds/ocm/commands/ocmcmds/common/options/keepglobaloption/option.go new file mode 100644 index 000000000..d3a5e7881 --- /dev/null +++ b/cmds/ocm/commands/ocmcmds/common/options/keepglobaloption/option.go @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +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" +) + +func From(o options.OptionSetProvider) *Option { + var opt *Option + o.AsOptionSet().Get(&opt) + return opt +} + +func New() *Option { + return &Option{} +} + +type Option struct { + KeepGlobalAccess bool +} + +var _ transferhandler.TransferOption = (*Option)(nil) + +func (o *Option) AddFlags(fs *pflag.FlagSet) { + fs.BoolVarP(&o.KeepGlobalAccess, "keep-global-access", "G", false, "preserve global access for value transport") +} + +func (o *Option) Usage() string { + s := ` +It the option --keep-global-access is given, all localized referential +resources will preserve their original global access information. +This behaviour can be further influenced by specifying a transfer script +with the script option family. +` + return s +} + +func (o *Option) ApplyTransferOption(opts transferhandler.TransferOptions) error { + return standard.KeepGlobalAccess(o.KeepGlobalAccess).ApplyTransferOption(opts) +} diff --git a/cmds/ocm/commands/ocmcmds/common/options/lookupoption/option.go b/cmds/ocm/commands/ocmcmds/common/options/lookupoption/option.go index 3a69a860c..39bc41b3d 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/lookupoption/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/lookupoption/option.go @@ -74,6 +74,10 @@ references. return s } +func (o *Option) IsGiven() bool { + return len(o.RepoSpecs) > 0 +} + func (o *Option) LookupComponentVersion(name string, vers string) (ocm.ComponentVersionAccess, error) { if o == nil || o.Resolver == nil { return nil, nil diff --git a/cmds/ocm/commands/ocmcmds/common/options/scriptoption/option.go b/cmds/ocm/commands/ocmcmds/common/options/scriptoption/option.go index f8a47729e..8a1b0b3a3 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/scriptoption/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/scriptoption/option.go @@ -38,7 +38,7 @@ func (o *Option) AddFlags(fs *pflag.FlagSet) { fs.StringVarP(&o.ScriptFile, "script", "", "", "config name of transfer handler script") } -func (o *Option) Complete(ctx clictx.Context) error { +func (o *Option) Configure(ctx clictx.Context) error { o.FileSystem = ctx.FileSystem() if o.ScriptFile != "" && o.Script != "" { return errors.Newf("only one of --script or --scriptFile may be set") diff --git a/cmds/ocm/commands/ocmcmds/common/options/signoption/option.go b/cmds/ocm/commands/ocmcmds/common/options/signoption/option.go index c80b1f2f5..7cc3b98c8 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/signoption/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/signoption/option.go @@ -80,7 +80,7 @@ func (o *Option) AddFlags(fs *pflag.FlagSet) { fs.StringArrayVarP(&o.rootca, "ca-cert", "", o.rootca, "Additional root certificates") } -func (o *Option) Complete(ctx clictx.Context) error { +func (o *Option) Configure(ctx clictx.Context) error { if len(o.SignatureNames) > 0 { for i, n := range o.SignatureNames { n = strings.TrimSpace(n) @@ -96,7 +96,7 @@ func (o *Option) Complete(ctx clictx.Context) error { o.Keys = signing.NewKeyRegistry() } if o.SignMode { - err := o.Hash.Complete(ctx) + err := o.Hash.Configure(ctx) if err != nil { return err } diff --git a/cmds/ocm/commands/ocmcmds/common/options/stoponexistingoption/option.go b/cmds/ocm/commands/ocmcmds/common/options/stoponexistingoption/option.go new file mode 100644 index 000000000..743fdb402 --- /dev/null +++ b/cmds/ocm/commands/ocmcmds/common/options/stoponexistingoption/option.go @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package stoponexistingoption + +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" +) + +func From(o options.OptionSetProvider) *Option { + var opt *Option + o.AsOptionSet().Get(&opt) + return opt +} + +func New() *Option { + return &Option{} +} + +type Option struct { + StopOnExistingVersion bool +} + +var _ transferhandler.TransferOption = (*Option)(nil) + +func (o *Option) AddFlags(fs *pflag.FlagSet) { + fs.BoolVarP(&o.StopOnExistingVersion, "stop-on-existing", "E", false, "stop on existing component version in target repository") +} + +func (o *Option) Usage() string { + s := ` +It the option --stop-on-existing is given together with the --recursive +option, the recursion is stopped for component versions already existing in the +target repository. This behaviour can be further influenced by specifying a transfer script +with the script option family. +` + return s +} + +func (o *Option) ApplyTransferOption(opts transferhandler.TransferOptions) error { + return standard.StopOnExistingVersion(o.StopOnExistingVersion).ApplyTransferOption(opts) +} diff --git a/cmds/ocm/commands/ocmcmds/common/options/templateroption/option.go b/cmds/ocm/commands/ocmcmds/common/options/templateroption/option.go index 9a5512507..dd7f20e2d 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/templateroption/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/templateroption/option.go @@ -24,6 +24,6 @@ type Option struct { template.Options } -func (o *Option) Complete(ctx clictx.Context) error { +func (o *Option) Configure(ctx clictx.Context) error { return o.Options.Complete(ctx.FileSystem()) } diff --git a/cmds/ocm/commands/ocmcmds/common/options/uploaderoption/option.go b/cmds/ocm/commands/ocmcmds/common/options/uploaderoption/option.go index e3d1737b6..36ef96eef 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/uploaderoption/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/uploaderoption/option.go @@ -47,7 +47,7 @@ func (o *Option) AddFlags(fs *pflag.FlagSet) { flag.StringToStringVarP(fs, &o.spec, "uploader", "", nil, "repository uploader (:[:]=[:[:]]") + MustFailWithMessage(opt.Configure(ctx), "invalid uploader registration plugin/name::: must be of [:[:]]") }) }) diff --git a/cmds/ocm/commands/ocmcmds/common/resources.go b/cmds/ocm/commands/ocmcmds/common/resources.go index ee84efc14..8268425e4 100644 --- a/cmds/ocm/commands/ocmcmds/common/resources.go +++ b/cmds/ocm/commands/ocmcmds/common/resources.go @@ -453,7 +453,12 @@ func ProcessElements(ictx inputs.Context, cv ocm.ComponentVersionAccess, elems [ if elem.Input().Input != nil { var acc ocm.AccessSpec // Local Blob - blob, hint, berr := elem.Input().Input.GetBlob(ictx, common.VersionedElementKey(cv), elem.Source().Origin()) + info := inputs.InputResourceInfo{ + ComponentVersion: common.VersionedElementKey(cv), + ElementName: elem.Spec().GetName(), + InputFilePath: elem.Source().Origin(), + } + blob, hint, berr := elem.Input().Input.GetBlob(ictx, info) if berr != nil { return errors.Wrapf(berr, "cannot get %s blob for %q(%s)", h.Key(), elem.Spec().GetName(), elem.Source()) } diff --git a/cmds/ocm/commands/ocmcmds/components/add/cmd.go b/cmds/ocm/commands/ocmcmds/components/add/cmd.go index feeba7ee6..d409a85c0 100644 --- a/cmds/ocm/commands/ocmcmds/components/add/cmd.go +++ b/cmds/ocm/commands/ocmcmds/components/add/cmd.go @@ -17,11 +17,12 @@ import ( "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/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" common2 "github.com/open-component-model/ocm/pkg/common" "github.com/open-component-model/ocm/pkg/common/accessio" @@ -30,6 +31,7 @@ import ( "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" "github.com/open-component-model/ocm/pkg/errors" ) @@ -41,8 +43,9 @@ var ( type Command struct { utils.BaseCommand - Force bool - Create bool + Force bool + Create bool + Closure bool Handler ctf.FormatHandler Format string @@ -61,7 +64,9 @@ func NewCommand(ctx clictx.Context, names ...string) *cobra.Command { fileoption.New("transport-archive"), schemaoption.New(compdesc.DefaultSchemeVersion), templateroption.New(""), - dryrunoption.New("evaluate and print component specifications", true)), + dryrunoption.New("evaluate and print component specifications", true), + lookupoption.New(), + rscbyvalueoption.New()), }, utils.Names(Names, names...)...) } @@ -114,6 +119,12 @@ content or a tar/tgz file (see option --type). If option --create is given, the archive is created first. An additional option --force will recreate an empty archive if it already exists. +If option --complete is given all component versions referenced by +the added one, will be added, also. Therefore, the --lookup is required +to specify an OCM repository to lookup the missing component versions. If +additionally the -V is given, the resources of those additional +components will be added by value. + The source, resource and reference list can be composed according the commands ocm add sources, ocm add resources, ocm add references, respectively. @@ -129,19 +140,19 @@ func (o *Command) AddFlags(fs *pflag.FlagSet) { o.BaseCommand.AddFlags(fs) fs.BoolVarP(&o.Force, "force", "f", false, "remove existing content") fs.BoolVarP(&o.Create, "create", "c", false, "(re)create archive") + fs.BoolVarP(&o.Closure, "complete", "C", false, "include all referenced component version") fs.StringArrayVarP(&o.Envs, "settings", "s", nil, "settings file with variable settings (yaml)") fs.StringVarP(&o.Version, "version", "v", "", "default version for components") } func (o *Command) Complete(args []string) error { - err := o.OptionSet.ProcessOnOptions(options.CompleteOptionsWithCLIContext(o.Context)) - if err != nil { - return err + if o.Closure && !lookupoption.From(o).IsGiven() { + return fmt.Errorf("lookup option required for option --complete") } o.Archive, args = fileoption.From(o).GetPath(args, o.Context.FileSystem()) t := templateroption.From(o) - err = t.ParseSettings(o.Context.FileSystem(), o.Envs...) + err := t.ParseSettings(o.Context.FileSystem(), o.Envs...) if err != nil { return err } @@ -165,6 +176,14 @@ func (o *Command) Complete(args []string) error { } func (o *Command) Run() error { + session := ocm.NewSession(nil) + defer session.Close() + + err := o.OptionSet.ProcessOnOptions(common.CompleteOptionsWithSession(o.Context, session)) + if err != nil { + return err + } + printer := common2.NewPrinter(o.Context.StdOut()) fs := o.Context.FileSystem() h := comp.NewResourceSpecHandler(o.Version) @@ -200,8 +219,13 @@ func (o *Command) Run() error { repo, err = ctf.Open(o.Context.OCMContext(), accessobj.ACC_WRITABLE, fp, mode, fs) } + thdlr, err := standard.New(standard.KeepGlobalAccess(), standard.Recursive(), rscbyvalueoption.From(o)) + if err != nil { + return err + } + if err == nil { - err = comp.ProcessComponents(o.Context, ictx, repo, h, elems) + err = comp.ProcessComponents(o.Context, ictx, repo, lookupoption.From(o).Resolver, thdlr, h, elems) cerr := repo.Close() if err == nil { err = cerr diff --git a/cmds/ocm/commands/ocmcmds/components/add/cmd_test.go b/cmds/ocm/commands/ocmcmds/components/add/cmd_test.go index 99e1e0f6e..fc7f4f0d6 100644 --- a/cmds/ocm/commands/ocmcmds/components/add/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/components/add/cmd_test.go @@ -7,7 +7,17 @@ package add_test 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/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/testhelper" + "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" + "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/testutils" "github.com/open-component-model/ocm/pkg/common/accessobj" @@ -16,8 +26,18 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" ) -func Check(env *TestEnv) { - repo := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, "ctf", 0, env)) +const OCIPATH = "/tmp/oci" +const OCIHOST = "alias" +const ARCH = "/tmp/ctf" +const LOOKUP = "/tmp/lookup" +const PROVIDER = "mandelsoft" +const VERSION = "v1" +const COMPONENT = "github.com/mandelsoft/test" +const COMPONENT2 = "github.com/mandelsoft/test2" +const OUT = "/tmp/res" + +func Check(env *TestEnv, handler func(ocm.Repository)) { + repo := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env)) defer Close(repo) cv := Must(repo.LookupComponentVersion("ocm.software/demo/test", "1.0.0")) defer Close(cv) @@ -36,6 +56,18 @@ func Check(env *TestEnv) { r := Must(cv.GetResource(metav1.Identity{"name": "data"})) data := Must(ocm.ResourceData(r)) Expect(string(data)).To(Equal("!stringdata")) + + Expect(cv.GetDescriptor().References).To(Equal(compdesc.References{{ + ElementMeta: compdesc.ElementMeta{ + Name: "ref", + Version: VERSION, + }, + ComponentName: COMPONENT2, + }})) + + if handler != nil { + handler(repo) + } } var _ = Describe("Test Environment", func() { @@ -50,14 +82,63 @@ var _ = Describe("Test Environment", func() { }) It("creates ctf and adds component", func() { - Expect(env.Execute("add", "c", "-fc", "--file", "ctf", "testdata/component.yaml")).To(Succeed()) - Expect(env.DirExists("ctf")).To(BeTrue()) - Check(env) + Expect(env.Execute("add", "c", "-fc", "--file", ARCH, "testdata/component.yaml")).To(Succeed()) + Expect(env.DirExists(ARCH)).To(BeTrue()) + Check(env, nil) }) It("creates ctf and adds components", func() { - Expect(env.Execute("add", "c", "-fc", "--file", "ctf", "--version", "1.0.0", "testdata/components.yaml")).To(Succeed()) - Expect(env.DirExists("ctf")).To(BeTrue()) - Check(env) + Expect(env.Execute("add", "c", "-fc", "--file", ARCH, "--version", "1.0.0", "testdata/components.yaml")).To(Succeed()) + Expect(env.DirExists(ARCH)).To(BeTrue()) + Check(env, nil) + }) + + Context("with completion", func() { + var ldesc *artdesc.Descriptor + + _ = ldesc + + BeforeEach(func() { + FakeOCIRepo(env.Builder, OCIPATH, OCIHOST) + + env.OCICommonTransport(OCIPATH, accessio.FormatDirectory, func() { + ldesc = OCIManifest1(env.Builder) + OCIManifest2(env.Builder) + }) + + env.OCMCommonTransport(LOOKUP, 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("image", "", resourcetypes.OCI_IMAGE, metav1.LocalRelation, func() { + env.Access( + ociartifact.New(oci.StandardOCIRef(OCIHOST+".alias", OCINAMESPACE, OCIVERSION)), + ) + }) + }) + }) + env.Component(COMPONENT2, func() { + env.Version(VERSION, func() { + env.Reference("ref", COMPONENT, VERSION) + env.Provider(PROVIDER) + }) + }) + }) + }) + + It("creates ctf and adds components", func() { + Expect(env.Execute("add", "c", "-fcCV", "--lookup", LOOKUP, "--file", ARCH, "testdata/component.yaml")).To(Succeed()) + Expect(env.DirExists(ARCH)).To(BeTrue()) + Check(env, func(repo ocm.Repository) { + cv := MustWithOffset(2, R(repo.LookupComponentVersion(COMPONENT, VERSION))) + defer Close(cv) + res := MustWithOffset(2, R(cv.GetResource(metav1.Identity{"name": "image"}))) + Expect(MustWithOffset(2, R(res.Access())).GetKind()).To(Equal(localblob.Type)) + Expect(MustWithOffset(2, R(res.Access())).GlobalAccessSpec(env.OCMContext()).GetKind()).To(Equal(ociartifact.Type)) + }) + }) }) }) diff --git a/cmds/ocm/commands/ocmcmds/components/add/testdata/component.yaml b/cmds/ocm/commands/ocmcmds/components/add/testdata/component.yaml index 2701d7d9a..a90dd6275 100644 --- a/cmds/ocm/commands/ocmcmds/components/add/testdata/component.yaml +++ b/cmds/ocm/commands/ocmcmds/components/add/testdata/component.yaml @@ -20,3 +20,8 @@ resources: input: type: binary data: IXN0cmluZ2RhdGE= + +componentReferences: + - name: ref + version: v1 + componentName: github.com/mandelsoft/test2 diff --git a/cmds/ocm/commands/ocmcmds/components/add/testdata/components.yaml b/cmds/ocm/commands/ocmcmds/components/add/testdata/components.yaml index e7abe01ad..fd1f6a6ac 100644 --- a/cmds/ocm/commands/ocmcmds/components/add/testdata/components.yaml +++ b/cmds/ocm/commands/ocmcmds/components/add/testdata/components.yaml @@ -20,3 +20,7 @@ components: input: type: binary data: IXN0cmluZ2RhdGE= + componentReferences: + - name: ref + version: v1 + componentName: github.com/mandelsoft/test2 \ No newline at end of file diff --git a/cmds/ocm/commands/ocmcmds/components/transfer/cmd.go b/cmds/ocm/commands/ocmcmds/components/transfer/cmd.go index 9d39e8dd8..de1e462f6 100644 --- a/cmds/ocm/commands/ocmcmds/components/transfer/cmd.go +++ b/cmds/ocm/commands/ocmcmds/components/transfer/cmd.go @@ -18,6 +18,7 @@ import ( "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/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" @@ -55,6 +56,7 @@ func NewCommand(ctx clictx.Context, names ...string) *cobra.Command { lookupoption.New(), overwriteoption.New(), rscbyvalueoption.New(), + stoponexistingoption.New(), uploaderoption.New(), scriptoption.New(), )}, utils.Names(Names, names...)...) @@ -108,9 +110,10 @@ func (o *Command) Run() error { thdlr, err := spiff.New( closureoption.From(o), + lookupoption.From(o), overwriteoption.From(o), rscbyvalueoption.From(o), - lookupoption.From(o), + stoponexistingoption.From(o), spiff.Script(scriptoption.From(o).ScriptData), spiff.ScriptFilesystem(o.FileSystem()), ) diff --git a/cmds/ocm/commands/ocmcmds/ctf/transfer/cmd.go b/cmds/ocm/commands/ocmcmds/ctf/transfer/cmd.go index 2b74ce502..b4725e6b9 100644 --- a/cmds/ocm/commands/ocmcmds/ctf/transfer/cmd.go +++ b/cmds/ocm/commands/ocmcmds/ctf/transfer/cmd.go @@ -7,11 +7,14 @@ package transfer 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/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/scriptoption" + "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" @@ -42,9 +45,12 @@ type Command struct { // NewCommand creates a new ctf command. func NewCommand(ctx clictx.Context, names ...string) *cobra.Command { return utils.SetupCommand(&Command{BaseCommand: utils.NewBaseCommand(ctx, + closureoption.New("component reference"), + lookupoption.New(), formatoption.New(), overwriteoption.New(), rscbyvalueoption.New(), + stoponexistingoption.New(), uploaderoption.New(), scriptoption.New(), )}, utils.Names(Names, names...)...) @@ -95,7 +101,10 @@ func (o *Command) Run() error { thdlr, err := spiff.New( spiff.Script(scriptoption.From(o).ScriptData), + closureoption.From(o), + lookupoption.From(o), rscbyvalueoption.From(o), + stoponexistingoption.From(o), overwriteoption.From(o), spiff.ScriptFilesystem(o.FileSystem()), ) diff --git a/cmds/ocm/commands/ocmcmds/names/names.go b/cmds/ocm/commands/ocmcmds/names/names.go index f71290603..ee3abe434 100644 --- a/cmds/ocm/commands/ocmcmds/names/names.go +++ b/cmds/ocm/commands/ocmcmds/names/names.go @@ -8,6 +8,7 @@ var ( ComponentArchive = []string{"componentarchive", "comparch", "ca"} CommonTransportArchive = []string{"commontransportarchive", "ctf"} Components = []string{"componentversions", "componentversion", "cv", "components", "component", "comps", "comp", "c"} + Configuration = []string{"configuration", "config", "cfg"} ResourceConfig = []string{"resource-configuration", "resourceconfig", "rsccfg", "rcfg"} SourceConfig = []string{"source-configuration", "sourceconfig", "srccfg", "scfg"} Resources = []string{"resources", "resource", "res", "r"} diff --git a/cmds/ocm/commands/ocmcmds/resources/add/cmd_test.go b/cmds/ocm/commands/ocmcmds/resources/add/cmd_test.go index 3ec5e44f4..edc0b452a 100644 --- a/cmds/ocm/commands/ocmcmds/resources/add/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/resources/add/cmd_test.go @@ -10,7 +10,6 @@ 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/testutils" diff --git a/cmds/ocm/commands/ocmcmds/sourceconfig/add/cmd_test.go b/cmds/ocm/commands/ocmcmds/sourceconfig/add/cmd_test.go index 6f636fa68..93d5aa8c8 100644 --- a/cmds/ocm/commands/ocmcmds/sourceconfig/add/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/sourceconfig/add/cmd_test.go @@ -70,7 +70,7 @@ access: repoUrl: github.com/open-component-model/ocm type: gitHub name: sources -type: filesystem +type: directoryTree `) }) diff --git a/cmds/ocm/commands/toicmds/cmd.go b/cmds/ocm/commands/toicmds/cmd.go index ff0a786e0..9326e592f 100644 --- a/cmds/ocm/commands/toicmds/cmd.go +++ b/cmds/ocm/commands/toicmds/cmd.go @@ -7,8 +7,10 @@ package toicmds import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/toicmds/components" + "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" @@ -35,9 +37,11 @@ installation data by th generic command. `, }, "toi") - cmd.AddCommand(components.NewCommand(ctx)) + cmd.AddCommand(_package.NewCommand(ctx)) + cmd.AddCommand(config.NewCommand(ctx)) cmd.AddCommand(bootstrap.NewCommand(ctx)) + cmd.AddCommand(describe.NewCommand(ctx)) cmd.AddCommand(topicocmrefs.New(ctx)) cmd.AddCommand(topicbootstrap.New(ctx, "bootstrapping")) diff --git a/cmds/ocm/commands/toicmds/config/bootstrap/cmd.go b/cmds/ocm/commands/toicmds/config/bootstrap/cmd.go new file mode 100644 index 000000000..b33e23694 --- /dev/null +++ b/cmds/ocm/commands/toicmds/config/bootstrap/cmd.go @@ -0,0 +1,231 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package bootstrap + +import ( + "fmt" + "strings" + + "github.com/mandelsoft/vfs/pkg/vfs" + "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/errors" + "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" +) + +const ( + DEFAULT_CREDENTIALS_FILE = bootstrap.DEFAULT_CREDENTIALS_FILE + DEFAULT_PARAMETER_FILE = bootstrap.DEFAULT_PARAMETER_FILE +) + +var ( + Names = names.Configuration + Verb = verbs.Bootstrap +) + +type Command struct { + utils.BaseCommand + Ref string + Id metav1.Identity + + CredentialsFile string + ParameterFile string +} + +// NewCommand creates a new bootstrap configuration command. +func NewCommand(ctx clictx.Context, names ...string) *cobra.Command { + return utils.SetupCommand(&Command{BaseCommand: utils.NewBaseCommand(ctx, repooption.New(), lookupoption.New())}, utils.Names(Names, names...)...) +} + +func (o *Command) ForName(name string) *cobra.Command { + cmd := &cobra.Command{ + Use: "[] {} {}", + Args: cobra.MinimumNArgs(1), + Short: "bootstrap TOI configuration files", + Long: ` +If a TOI package provides information for configuration file templates/prototypes +this command extracts this data and provides appropriate files in the filesystem. + +The package resource must have the type ` + toi.TypeTOIPackage + `. +This is a simple YAML file resource describing the bootstrapping of a dedicated kind +of software. See also the topic ocm toi toi-bootstrapping. + +The first matching resource of this type is selected. Optionally a set of +identity attribute can be specified used to refine the match. This can be the +resource name and/or other key/value pairs (<attr>=<value>). + +If no credentials file name is provided (option -c) the file +` + DEFAULT_CREDENTIALS_FILE + ` is used. If no parameter file name is +provided (option -p) the file ` + DEFAULT_PARAMETER_FILE + ` is used. + +For more details about those files see ocm bootstrap package. +`, + Example: ` +$ ocm toi bootstrap config ghcr.io/mandelsoft/ocm//ocmdemoinstaller:0.0.1-dev +`, + } + cmd.AddCommand(topicbootstrap.New(o.Context, "toi-bootstrapping")) + return cmd +} + +func (o *Command) AddFlags(fs *pflag.FlagSet) { + o.BaseCommand.AddFlags(fs) + fs.StringVarP(&o.CredentialsFile, "credentials", "c", DEFAULT_CREDENTIALS_FILE, "credentials file name") + fs.StringVarP(&o.ParameterFile, "parameters", "p", DEFAULT_PARAMETER_FILE, "parameter file name") +} + +func (o *Command) Complete(args []string) error { + o.Ref = args[0] + id, err := ocmcommon.MapArgsToIdentityPattern(args[1:]...) + if err != nil { + return errors.Wrapf(err, "bootstrap resource identity pattern") + } + o.Id = id + return nil +} + +func (o *Command) Run() error { + session := ocm.NewSession(nil) + defer session.Close() + + err := o.ProcessOnOptions(ocmcommon.CompleteOptionsWithSession(o, session)) + if err != nil { + return err + } + handler := comphdlr.NewTypeHandler(o.Context.OCM(), session, repooption.From(o).Repository) + return utils.HandleOutput(&action{cmd: o}, handler, utils.StringElemSpecs(o.Ref)...) +} + +//////////////////////////////////////////////////////////////////////////////// + +type action struct { + data comphdlr.Objects + cmd *Command +} + +var _ output.Output = (*action)(nil) + +func (a *action) Add(e interface{}) error { + if len(a.data) > 0 { + return errors.New("found multiple component versions") + } + o, ok := e.(*comphdlr.Object) + if !ok { + return fmt.Errorf("object of type %T is not a valid comphdlr.Object", e) + } + if o.ComponentVersion != nil && !ocireg.IsKind(o.Repository.GetSpecification().GetKind()) { + if ociuploadattr.Get(a.cmd.Context) == nil { + out.Outf(a.cmd, "Warning: repository is no OCI registry, consider importing it or use upload repository with option ' -X ociuploadrepo=...\n") + } else { + out.Outf(a.cmd, "Warning: repository is no OCI registry, consider importing.\n'") + } + } + a.data = append(a.data, o) + return nil +} + +func (a *action) Close() error { + return nil +} + +type Binary struct { + Binary []byte `json:"binary"` +} + +func (a *action) Out() error { + cv := a.data[0].ComponentVersion + nv := common.VersionedElementKey(cv) + rid := metav1.NewResourceRef(a.cmd.Id) + resolver := lookupoption.From(a.cmd) + + ires, eff, err := utils2.MatchResourceReference(cv, toi.TypeTOIPackage, rid, resolver) + if err != nil { + return errors.Wrapf(err, "package resource in %s", nv) + } + defer eff.Close() + out.Outf(a.cmd.Context, "found package resource %q in %s\n", ires.Meta().GetName(), nv) + + var spec toi.PackageSpecification + err = install.GetResource(ires, &spec) + if err != nil { + return errors.ErrInvalidWrap(err, "package spec") + } + + if spec.Description != "" { + out.Outf(a.cmd.Context, "\nPackage Description:\n%s\n\n", utils3.IndentLines(spec.Description, " ", false)) + } + if spec.AdditionalResources == nil { + out.Outf(a.cmd.Context, "no configuration templates found for %s in %s\n", ires.Meta().GetName(), nv) + return nil + } + + err = nil + if len(spec.Scheme) > 0 && a.cmd.ParameterFile != "" { + schemeFile := a.cmd.ParameterFile + if strings.HasSuffix(schemeFile, ".yaml") { + schemeFile = schemeFile[:len(schemeFile)-5] + } + schemeFile = schemeFile + ".jsonscheme" + err = vfs.WriteFile(a.cmd.FileSystem(), schemeFile, spec.Scheme, 0o644) + if err != nil { + out.Errf(a.cmd.Context, "writing scheme file %s failed: %s\n", schemeFile, err) + } else { + out.Outf(a.cmd.Context, "%s: %d byte(s) written\n", schemeFile, len(spec.Scheme)) + } + } + if a.download("configuration template", a.cmd.ParameterFile, cv, spec.AdditionalResources[toi.AdditionalResourceConfigFile], resolver) != nil { + err = fmt.Errorf("download failed") + } + + if e := a.download("credentials template", a.cmd.CredentialsFile, cv, spec.AdditionalResources[toi.AdditionalResourceCredentialsFile], resolver); e != nil { + out.Errf(a.cmd.Context, "%s", e.Error()) + err = fmt.Errorf("download failed") + } + + return err +} + +func (a *action) download(kind, path string, cv ocm.ComponentVersionAccess, spec *metav1.ResourceReference, resolver ocm.ComponentVersionResolver) error { + + if spec == nil { + out.Outf(a.cmd.Context, "no %s configured\n", kind) + return nil + } + res, _, err := utils2.MatchResourceReference(cv, toi.TypeYAML, *spec, resolver) + if err != nil { + return errors.Wrapf(err, "%s resource", kind) + } + out.Outf(a.cmd.Context, "downloading %s...\n", kind) + ok, path, err := download.For(a.cmd.Context).DownloadAsBlob(common.NewPrinter(a.cmd.StdOut()), res, path, a.cmd.FileSystem()) + if err != nil { + return err + } + if !ok { + return errors.Newf("no downloader configured for type %q", res.Meta().GetType()) + } + return nil +} diff --git a/cmds/ocm/commands/toicmds/config/cmd.go b/cmds/ocm/commands/toicmds/config/cmd.go new file mode 100644 index 000000000..e1c047500 --- /dev/null +++ b/cmds/ocm/commands/toicmds/config/cmd.go @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +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" +) + +var Names = names.Configuration + +// NewCommand creates a new command. +func NewCommand(ctx clictx.Context) *cobra.Command { + cmd := utils.MassageCommand(&cobra.Command{ + Short: "TOI Commands acting on config", + }, Names...) + AddCommands(ctx, cmd) + return cmd +} + +func AddCommands(ctx clictx.Context, cmd *cobra.Command) { + cmd.AddCommand(bootstrap.NewCommand(ctx, bootstrap.Verb)) +} diff --git a/cmds/ocm/commands/toicmds/names/names.go b/cmds/ocm/commands/toicmds/names/names.go index 7edeba826..911bb187d 100644 --- a/cmds/ocm/commands/toicmds/names/names.go +++ b/cmds/ocm/commands/toicmds/names/names.go @@ -8,4 +8,5 @@ import ( "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" ) -var Components = names.Components +var Package = []string{"package", "pkg", "componentversion", "cv", "component", "comp", "c"} +var Configuration = names.Configuration diff --git a/cmds/ocm/commands/toicmds/components/bootstrap/cmd.go b/cmds/ocm/commands/toicmds/package/bootstrap/cmd.go similarity index 77% rename from cmds/ocm/commands/toicmds/components/bootstrap/cmd.go rename to cmds/ocm/commands/toicmds/package/bootstrap/cmd.go index fca17e24c..7d0eef0e2 100644 --- a/cmds/ocm/commands/toicmds/components/bootstrap/cmd.go +++ b/cmds/ocm/commands/toicmds/package/bootstrap/cmd.go @@ -29,6 +29,7 @@ import ( "github.com/open-component-model/ocm/pkg/errors" "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/install" ) @@ -39,7 +40,7 @@ const ( ) var ( - Names = names.Components + Names = names.Package Verb = verbs.Bootstrap ) @@ -56,7 +57,7 @@ type Command struct { Parameters accessio.DataSource } -// NewCommand creates a new ctf command. +// NewCommand creates a new bootstrap component command. func NewCommand(ctx clictx.Context, names ...string) *cobra.Command { return utils.SetupCommand(&Command{BaseCommand: utils.NewBaseCommand(ctx, repooption.New(), lookupoption.New())}, utils.Names(Names, names...)...) } @@ -71,11 +72,11 @@ Use the simple TOI bootstrap mechanism to execute actions for a TOI package reso based on the content of an OCM component version and some command input describing the dedicated installation target. -The package resource must have the type ` + install.TypeTOIPackage + `. +The package resource must have the type ` + toi.TypeTOIPackage + `. This is a simple YAML file resource describing the bootstrapping of a dedicated kind of software. See also the topic ocm toi toi-bootstrapping. -THis resource finally describes an executor image, which will be executed in a +This resource finally describes an executor image, which will be executed in a container with the installation source and (instance specific) user settings. The container is just executed, the framework make no assumption about the meaning/outcome of the execution. Therefore, any kind of actions can be described and @@ -93,19 +94,69 @@ file. If no credentials file name is provided (option -c) the file ` + DEFAULT_CREDENTIALS_FILE + ` is used, if present. If no parameter file name is provided (option -p) the file ` + DEFAULT_PARAMETER_FILE + ` is used, if present. + +Using the credentials file it is possible to configure credentials required by +the installation package or executor. Additionally arbitrary consumer ids +can be forwarded to executor, which might be required by accessing blobs +described by external access methods. + +The credentials file uses the following yaml format: +- credentials *map[string]CredentialsSpec* + + The resolution of credentials requested by the package (by name). + +- forwardedConsumers *[]ForwardSpec* (optional) + + An optional list of consumer specifications to be forwarded to the OCM + configuration provided to the executor. + +The *CredentialsSpec* uses the following format: + +- consumerId *map[string]string* + + The consumer id used to look up the credentials. + +- consumerType *string* (optional) (default: partial) + + The type of the matcher used to match the consumer id. + +- reference *yaml* + + A generic credential specification as used in the ocm config file. + +- credentials *map[string]string* + + Direct credential fields. + +One of consumerId, reference or credentials +must be configured. + +The *ForwardSpec* uses the following format: + +- consumerId *map[string]string* + + The consumer id to be forwarded. + +- consumerType *string* (optional) (default: partial) + + The type of the matcher used to match the consumer id. + +If provided by the package it is possible to download template versions +for the parameter and credentials file using the command ocm bootstrap configuration. `, Example: ` -$ ocm toi bootstrap componentversion ghcr.io/mandelsoft/ocmdemoinstaller:0.0.1-dev +$ ocm toi bootstrap package ghcr.io/mandelsoft/ocm//ocmdemoinstaller:0.0.1-dev `, } cmd.AddCommand(topicbootstrap.New(o.Context, "toi-bootstrapping")) return cmd } -func (o *Command) AddFlags(set *pflag.FlagSet) { - set.StringVarP(&o.CredentialsFile, "credentials", "c", "", "credentials file") - set.StringVarP(&o.ParameterFile, "parameters", "p", "", "parameter file") - set.StringVarP(&o.OutputFile, "outputs", "o", "", "output file/directory") +func (o *Command) AddFlags(fs *pflag.FlagSet) { + o.BaseCommand.AddFlags(fs) + fs.StringVarP(&o.CredentialsFile, "credentials", "c", "", "credentials file") + fs.StringVarP(&o.ParameterFile, "parameters", "p", "", "parameter file") + fs.StringVarP(&o.OutputFile, "outputs", "o", "", "output file/directory") } func (o *Command) Complete(args []string) error { diff --git a/cmds/ocm/commands/toicmds/components/cmd.go b/cmds/ocm/commands/toicmds/package/cmd.go similarity index 91% rename from cmds/ocm/commands/toicmds/components/cmd.go rename to cmds/ocm/commands/toicmds/package/cmd.go index e25ff6607..82f21b635 100644 --- a/cmds/ocm/commands/toicmds/components/cmd.go +++ b/cmds/ocm/commands/toicmds/package/cmd.go @@ -2,18 +2,18 @@ // // SPDX-License-Identifier: Apache-2.0 -package components +package _package import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/toicmds/components/bootstrap" "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" ) -var Names = names.Components +var Names = names.Package // NewCommand creates a new command. func NewCommand(ctx clictx.Context) *cobra.Command { diff --git a/cmds/ocm/commands/toicmds/package/describe/cmd.go b/cmds/ocm/commands/toicmds/package/describe/cmd.go new file mode 100644 index 000000000..8d731eceb --- /dev/null +++ b/cmds/ocm/commands/toicmds/package/describe/cmd.go @@ -0,0 +1,272 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package describe + +import ( + "fmt" + "strings" + + "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/errors" + "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" +) + +const ( + DEFAULT_CREDENTIALS_FILE = bootstrap.DEFAULT_CREDENTIALS_FILE + DEFAULT_PARAMETER_FILE = bootstrap.DEFAULT_PARAMETER_FILE +) + +var ( + Names = names.Package + Verb = verbs.Describe +) + +type Command struct { + utils.BaseCommand + Ref string + Id metav1.Identity +} + +// NewCommand creates a new bootstrap configuration command. +func NewCommand(ctx clictx.Context, names ...string) *cobra.Command { + return utils.SetupCommand(&Command{BaseCommand: utils.NewBaseCommand(ctx, repooption.New(), lookupoption.New())}, utils.Names(Names, names...)...) +} + +func (o *Command) ForName(name string) *cobra.Command { + cmd := &cobra.Command{ + Use: "[] {} {}", + Args: cobra.MinimumNArgs(1), + Short: "describe TOI package", + Long: ` +Describe a TOI package provided by a resource of an OCM component version. + +The package resource must have the type ` + toi.TypeTOIPackage + `. +This is a simple YAML file resource describing the bootstrapping of a dedicated kind +of software. See also the topic ocm toi toi-bootstrapping. + +The first matching resource of this type is selected. Optionally a set of +identity attribute can be specified used to refine the match. This can be the +resource name and/or other key/value pairs (<attr>=<value>). +`, + Example: ` +$ ocm toi describe package ghcr.io/mandelsoft/ocm//ocmdemoinstaller:0.0.1-dev +`, + } + cmd.AddCommand(topicbootstrap.New(o.Context, "toi-bootstrapping")) + return cmd +} + +func (o *Command) Complete(args []string) error { + o.Ref = args[0] + id, err := ocmcommon.MapArgsToIdentityPattern(args[1:]...) + if err != nil { + return errors.Wrapf(err, "bootstrap resource identity pattern") + } + o.Id = id + return nil +} + +func (o *Command) Run() error { + session := ocm.NewSession(nil) + defer session.Close() + + err := o.ProcessOnOptions(ocmcommon.CompleteOptionsWithSession(o, session)) + if err != nil { + return err + } + handler := comphdlr.NewTypeHandler(o.Context.OCM(), session, repooption.From(o).Repository) + return utils.HandleOutput(&action{cmd: o}, handler, utils.StringElemSpecs(o.Ref)...) +} + +//////////////////////////////////////////////////////////////////////////////// + +type action struct { + data comphdlr.Objects + cmd *Command +} + +var _ output.Output = (*action)(nil) + +func (a *action) Add(e interface{}) error { + o, ok := e.(*comphdlr.Object) + if !ok { + return fmt.Errorf("object of type %T is not a valid comphdlr.Object", e) + } + if o.ComponentVersion != nil && !ocireg.IsKind(o.Repository.GetSpecification().GetKind()) { + if ociuploadattr.Get(a.cmd.Context) == nil { + out.Outf(a.cmd, "Warning: repository is no OCI registry, consider importing it or use upload repository with option ' -X ociuploadrepo=...\n") + } else { + out.Outf(a.cmd, "Warning: repository is no OCI registry, consider importing.\n'") + } + } + a.data = append(a.data, o) + return nil +} + +func (a *action) Close() error { + return nil +} + +type Binary struct { + Binary []byte `json:"binary"` +} + +func (a *action) Out() error { + cnt := 0 + for i := range a.data { + if i > 0 { + a.Outf("\n") + } + nv := common.VersionedElementKey(a.data[i].ComponentVersion) + err := a.describe(a.data[i].ComponentVersion) + if err != nil { + out.Errf(a.cmd.Context, "%s: %s\n", nv, err) + cnt++ + } + } + if cnt > 0 { + return fmt.Errorf("describe failed for %d packages", cnt) + } + return nil +} + +func (a *action) Outf(msg string, args ...interface{}) { + out.Outf(a.cmd.Context, msg, args...) +} + +type einfo struct { + index int + ectx *install.ExecutorContext +} + +func (a *action) describe(cv ocm.ComponentVersionAccess) error { + nv := common.VersionedElementKey(cv) + rid := metav1.NewResourceRef(a.cmd.Id) + resolver := lookupoption.From(a.cmd) + + ires, eff, err := utils2.MatchResourceReference(cv, toi.TypeTOIPackage, rid, resolver) + if err != nil { + return errors.Wrapf(err, "package resource in %s", nv) + } + defer eff.Close() + + var spec toi.PackageSpecification + err = install.GetResource(ires, &spec) + if err != nil { + return errors.ErrInvalidWrap(err, "package spec") + } + + a.Outf("TOI Package %s[%s]\n", nv, ires.Meta().GetName()) + + if spec.Description != "" { + a.Outf(" Package Description:\n%s\n\n", utils3.IndentLines(strings.TrimSpace(spec.Description), " ", false)) + } + if len(spec.AdditionalResources) == 0 { + a.Outf(" no additional resources found\n") + } else { + keys := utils3.StringMapKeys(spec.AdditionalResources) + a.Outf(" Additional Resources:\n") + for _, k := range keys { + switch k { + case toi.AdditionalResourceCredentialsFile: + out.Outf(a.cmd.Context, " - %s: %s\n", k, "downloadable credential file template)") + case toi.AdditionalResourceConfigFile: + out.Outf(a.cmd.Context, " - %s: %s\n", k, "downloadable user configuration file template)") + default: + out.Outf(a.cmd.Context, " - %s\n", k) + } + } + } + + actions := map[string]*einfo{} + + for i, e := range spec.Executors { + eacts := e.Actions + + ectx, err := install.DetermineExecutor(&e, a.cmd.OCMContext(), cv, resolver) + if err != nil { + a.Outf(" Warning: cannot determine executor %s: %s\n", e.Name(), err) + } + info := &einfo{i, ectx} + if len(eacts) == 0 && ectx != nil { + eacts = ectx.Spec.Actions + } + if len(eacts) == 0 { + eacts = []string{""} + } + for o, n := range eacts { + if _, ok := actions[n]; ok { + a.Outf(" Warning: action %s defined for multiple executors: %d and %d\n", n, o, i) + } else { + actions[n] = info + } + } + } + if len(actions) == 0 { + a.Outf(" Warning: no actions defined\n") + } else { + keys := utils3.StringMapKeys(actions) + a.Outf(" Supported Actions:\n") + for _, k := range keys { + info := actions[k] + exec := spec.Executors[info.index] + out.Outf(a.cmd.Context, " - %s: provided by %s\n", k, exec.Name()) + if len(exec.CredentialMapping) > 0 { + ckeys := utils3.StringMapKeys(exec.CredentialMapping) + out.Outf(a.cmd.Context, " credential key mappings\n") + for _, c := range ckeys { + out.Outf(a.cmd.Context, " - %s: %s\n", c, exec.CredentialMapping[c]) + } + } + } + } + if len(spec.Credentials) == 0 { + a.Outf(" no credentials required\n") + } else { + keys := utils3.StringMapKeys(spec.Credentials) + a.Outf(" Required Credentials:\n") + for _, k := range keys { + cred := spec.Credentials[k] + opt := "" + if cred.Optional { + opt = " (optional)" + } + out.Outf(a.cmd.Context, " - %s%s\n", k, opt) + out.Outf(a.cmd.Context, " description: %s\n", utils3.IndentLines(strings.TrimSpace(cred.Description), " ", true)) + if len(cred.ConsumerId) != 0 { + out.Outf(a.cmd.Context, " used as consumer id: %s\n", cred.ConsumerId) + } + if len(cred.Properties) != 0 { + ckeys := utils3.StringMapKeys(cred.Properties) + out.Outf(a.cmd.Context, " required properties:\n") + for _, c := range ckeys { + out.Outf(a.cmd.Context, " - %s: %s\n", c, utils3.IndentLines(strings.TrimSpace(cred.Properties[c]), " ", true)) + } + } + } + } + return nil +} diff --git a/cmds/ocm/commands/toicmds/verbs/bootstrap/cmd.go b/cmds/ocm/commands/toicmds/verbs/bootstrap/cmd.go index ce66884a5..4160dc3db 100644 --- a/cmds/ocm/commands/toicmds/verbs/bootstrap/cmd.go +++ b/cmds/ocm/commands/toicmds/verbs/bootstrap/cmd.go @@ -7,7 +7,7 @@ package bootstrap import ( "github.com/spf13/cobra" - components "github.com/open-component-model/ocm/cmds/ocm/commands/toicmds/components/bootstrap" + 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" diff --git a/cmds/ocm/commands/toicmds/verbs/describe/cmd.go b/cmds/ocm/commands/toicmds/verbs/describe/cmd.go new file mode 100644 index 000000000..c7c1f044c --- /dev/null +++ b/cmds/ocm/commands/toicmds/verbs/describe/cmd.go @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +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" +) + +// NewCommand creates a new command. +func NewCommand(ctx clictx.Context) *cobra.Command { + cmd := utils.MassageCommand(&cobra.Command{ + Short: "describe packages", + }, verbs.Describe) + cmd.AddCommand(_package.NewCommand(ctx)) + return cmd +} diff --git a/cmds/ocm/commands/verbs/bootstrap/cmd.go b/cmds/ocm/commands/verbs/bootstrap/cmd.go index ce66884a5..8fae1eb05 100644 --- a/cmds/ocm/commands/verbs/bootstrap/cmd.go +++ b/cmds/ocm/commands/verbs/bootstrap/cmd.go @@ -7,7 +7,8 @@ package bootstrap import ( "github.com/spf13/cobra" - components "github.com/open-component-model/ocm/cmds/ocm/commands/toicmds/components/bootstrap" + 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" @@ -18,6 +19,7 @@ func NewCommand(ctx clictx.Context) *cobra.Command { cmd := utils.MassageCommand(&cobra.Command{ Short: "bootstrap components", }, verbs.Bootstrap) - cmd.AddCommand(components.NewCommand(ctx)) + cmd.AddCommand(_package.NewCommand(ctx)) + cmd.AddCommand(config.NewCommand(ctx)) return cmd } diff --git a/cmds/ocm/commands/verbs/describe/cmd.go b/cmds/ocm/commands/verbs/describe/cmd.go index 2e4f7047a..88823de94 100644 --- a/cmds/ocm/commands/verbs/describe/cmd.go +++ b/cmds/ocm/commands/verbs/describe/cmd.go @@ -9,6 +9,7 @@ import ( 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" @@ -21,5 +22,6 @@ func NewCommand(ctx clictx.Context) *cobra.Command { }, verbs.Describe) cmd.AddCommand(resources.NewCommand(ctx)) cmd.AddCommand(plugins.NewCommand(ctx)) + cmd.AddCommand(_package.NewCommand(ctx)) return cmd } diff --git a/cmds/ocm/pkg/options/interfaces.go b/cmds/ocm/pkg/options/interfaces.go index 5dbdabc0e..bee83e6fa 100644 --- a/cmds/ocm/pkg/options/interfaces.go +++ b/cmds/ocm/pkg/options/interfaces.go @@ -24,7 +24,7 @@ type OptionWithOutputContextCompleter interface { } type OptionWithCLIContextCompleter interface { - Complete(ctx clictx.Context) error + Configure(ctx clictx.Context) error } type Usage interface { @@ -161,7 +161,7 @@ func CompleteOptions(opt Options) error { func CompleteOptionsWithCLIContext(ctx clictx.Context) OptionsProcessor { return func(opt Options) error { if c, ok := opt.(OptionWithCLIContextCompleter); ok { - return c.Complete(ctx) + return c.Configure(ctx) } if c, ok := opt.(OptionWithOutputContextCompleter); ok { return c.Complete(ctx) diff --git a/cmds/ocm/pkg/output/options.go b/cmds/ocm/pkg/output/options.go index 8a2e69a61..c6b36695c 100644 --- a/cmds/ocm/pkg/output/options.go +++ b/cmds/ocm/pkg/output/options.go @@ -97,7 +97,7 @@ func (o *Options) AddFlags(fs *pflag.FlagSet) { o.OptionSet.AddFlags(fs) } -func (o *Options) Complete(ctx clictx.Context) error { +func (o *Options) Configure(ctx clictx.Context) error { o.Context = ctx // process sub options first, to assure that output options are available for output @@ -138,7 +138,7 @@ func (o *Options) Complete(ctx clictx.Context) error { } func (o *Options) CompleteAll(ctx clictx.Context) error { - err := o.Complete(ctx) + err := o.Configure(ctx) if err == nil { err = o.OptionSet.ProcessOnOptions(options.CompleteOptionsWithCLIContext(ctx)) } diff --git a/cmds/ocm/pkg/processing/logging.go b/cmds/ocm/pkg/processing/logging.go new file mode 100644 index 000000000..660907b75 --- /dev/null +++ b/cmds/ocm/pkg/processing/logging.go @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package processing + +import ( + ocmlog "github.com/open-component-model/ocm/pkg/logging" +) + +var REALM = ocmlog.DefineSubRealm("output processing chains", "processing") + +var logger = ocmlog.DynamicLogger() diff --git a/cmds/ocm/pkg/processing/processing_test.go b/cmds/ocm/pkg/processing/processing_test.go index 9591b7617..44532a79f 100644 --- a/cmds/ocm/pkg/processing/processing_test.go +++ b/cmds/ocm/pkg/processing/processing_test.go @@ -18,19 +18,19 @@ import ( "github.com/open-component-model/ocm/pkg/testutils" ) -var AddOne = func(log logging.Context) func(e interface{}) interface{} { +var AddOne = func(logger logging.Logger) func(e interface{}) interface{} { return func(e interface{}) interface{} { - log.Logger().Info("add one to number", "num", e.(int)) + logger.Info("add one to number", "num", e.(int)) return e.(int) + 1 } } -var Mul = func(log logging.Context) func(n, fac int) ExplodeFunction { +var Mul = func(logger logging.Logger) func(n, fac int) ExplodeFunction { return func(n, fac int) ExplodeFunction { return func(e interface{}) []interface{} { r := []interface{}{} v := e.(int) - log.Logger().Info("explode", "num", e.(int)) + logger.Info("explode", "num", e.(int)) for i := 1; i <= n; i++ { r = append(r, v) v = v * fac @@ -42,19 +42,21 @@ var Mul = func(log logging.Context) func(n, fac int) ExplodeFunction { var _ = Describe("simple data processing", func() { var ( - log logging.Context - buf *bytes.Buffer + log logging.Context + logger logging.Logger + buf *bytes.Buffer ) BeforeEach(func() { log, buf = ocmlog.NewBufferedContext() + logger = log.Logger() }) Context("sequential", func() { It("map", func() { By("*** sequential map") data := data.IndexedSliceAccess([]interface{}{1, 2, 3}) - result := Chain(log).Map(AddOne(log)).Process(data).AsSlice() + result := Chain(log).Map(AddOne(logger)).Process(data).AsSlice() Expect([]interface{}(result)).To(Equal([]interface{}{2, 3, 4})) Expect(buf.String()).To(testutils.StringEqualTrimmedWithContext(` @@ -67,7 +69,7 @@ V[3] add one to number num 3 It("explode", func() { By("*** sequential explode") data := data.IndexedSliceAccess([]interface{}{1, 2, 3}) - result := Chain(log).Map(AddOne(log)).Explode(Mul(log)(3, 2)).Map(Identity).Process(data).AsSlice() + result := Chain(log).Map(AddOne(logger)).Explode(Mul(logger)(3, 2)).Map(Identity).Process(data).AsSlice() Expect([]interface{}(result)).To(Equal([]interface{}{ 2, 4, 8, 3, 6, 12, @@ -79,7 +81,7 @@ V[3] add one to number num 3 It("map", func() { By("*** parallel map") data := data.IndexedSliceAccess([]interface{}{1, 2, 3}) - result := Chain(log).Map(Identity).Parallel(3).Map(AddOne(log)).Process(data).AsSlice() + result := Chain(log).Map(Identity).Parallel(3).Map(AddOne(logger)).Process(data).AsSlice() Expect([]interface{}(result)).To(Equal([]interface{}{ 2, 3, 4, })) @@ -88,7 +90,7 @@ V[3] add one to number num 3 By("*** parallel explode") data := data.IndexedSliceAccess([]interface{}{1, 2, 3}) - result := Chain(log).Parallel(3).Explode(Mul(log)(3, 2)).Process(data).AsSlice() + result := Chain(log).Parallel(3).Explode(Mul(logger)(3, 2)).Process(data).AsSlice() Expect([]interface{}(result)).To(Equal([]interface{}{ 1, 2, 4, 2, 4, 8, @@ -99,7 +101,7 @@ V[3] add one to number num 3 By("*** parallel explode") data := data.IndexedSliceAccess([]interface{}{1, 2, 3}) - result := Chain(log).Parallel(3).Explode(Mul(log)(3, 2)).Map(AddOne(log)).Process(data).AsSlice() + result := Chain(log).Parallel(3).Explode(Mul(logger)(3, 2)).Map(AddOne(logger)).Process(data).AsSlice() Expect([]interface{}(result)).To(Equal([]interface{}{ 2, 3, 5, 3, 5, 9, @@ -109,9 +111,9 @@ V[3] add one to number num 3 }) Context("compose", func() { It("appends a chain", func() { - chain := Chain(log).Map(AddOne(log)) + chain := Chain(log).Map(AddOne(logger)) slice := data.IndexedSliceAccess([]interface{}{1, 2, 3}) - sub := Chain(log).Explode(Mul(log)(2, 2)) + sub := Chain(log).Explode(Mul(logger)(2, 2)) r := chain.Append(sub).Process(slice).AsSlice() Expect(r).To(Equal(data.IndexedSliceAccess([]interface{}{ 2, 4, 3, 6, 4, 8, diff --git a/cmds/ocm/pkg/utils/listhelp.go b/cmds/ocm/pkg/utils/listhelp.go index f69ff0580..a609ae1be 100644 --- a/cmds/ocm/pkg/utils/listhelp.go +++ b/cmds/ocm/pkg/utils/listhelp.go @@ -21,6 +21,25 @@ func FormatList(def string, elems ...string) string { return FormatListElements(def, StringElementList(elems)) } +type maplist[E any] struct { + desc func(E) string + keys []string + m map[string]E +} + +func (l *maplist[E]) Size() int { return len(l.keys) } +func (l *maplist[E]) Key(i int) string { return l.keys[i] } +func (l *maplist[E]) Description(i int) string { return l.desc(l.m[l.keys[i]]) } + +func FormatMapElements[E any](def string, m map[string]E, desc func(E) string) string { + keys := utils.StringMapKeys(m) + return FormatListElements(def, &maplist[E]{ + desc: desc, + keys: keys, + m: m, + }) +} + type ListElements interface { Size() int Key(i int) string diff --git a/cmds/ocm/topics/common/logging/topic.go b/cmds/ocm/topics/common/logging/topic.go new file mode 100644 index 000000000..1bcee5710 --- /dev/null +++ b/cmds/ocm/topics/common/logging/topic.go @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package logging + +import ( + "fmt" + "strings" + + "github.com/mandelsoft/logging" + "github.com/spf13/cobra" + + utils2 "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" + logcfg "github.com/open-component-model/ocm/pkg/contexts/datacontext/config/logging" +) + +func New(ctx clictx.Context) *cobra.Command { + return &cobra.Command{ + Use: "logging", + Short: "Configured logging keys", + Example: ` + type: ` + logcfg.ConfigType + ` + contextType: ` + datacontext.CONTEXT_TYPE + ` + settings: + defaultLevel: Info + rules: + - ... +`, + Long: ` +Logging can be configured as part of the ocm config file (ocm configfile) +or by command line options of the ocm command. Details about +the YAML structure of a logging settings can be found on https://github.com/mandelsoft/logging. + +The command line also supports some quick-config options for enabling log levels +for dedicated tags and realms (logging keys). + +` + describe("tags", logging.GetTagDefinitions()) + ` + +` + describe("realms", logging.GetRealmDefinitions()), + } +} + +func describe(name string, defs logging.Definitions) string { + if len(defs) == 0 { + return fmt.Sprintf("There are no defined *%s*.", name) + } + return fmt.Sprintf(`The following *%s* are used by the command line tool: +%s +`, name, utils2.FormatMapElements("", defs, func(e []string) string { + return strings.Join(e, ", ") + })) +} diff --git a/cmds/ocm/topics/toi/bootstrapping/topic.go b/cmds/ocm/topics/toi/bootstrapping/topic.go index 2d804d214..2605e72d2 100644 --- a/cmds/ocm/topics/toi/bootstrapping/topic.go +++ b/cmds/ocm/topics/toi/bootstrapping/topic.go @@ -10,7 +10,7 @@ import ( "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/install" + "github.com/open-component-model/ocm/pkg/toi" ) func New(ctx clictx.Context, name string) *cobra.Command { @@ -18,6 +18,8 @@ func New(ctx clictx.Context, name string) *cobra.Command { Use: name, Short: "Tiny OCM Installer based on component versions", Example: ` +description: | + This package is just an example. executors: - actions: - install @@ -56,6 +58,10 @@ configScheme: type: string password: type: string +additionalResources: + configFile: + resource: + name: config-file `, Long: ` TOI is a small toolset on top of the Open Component Model. It provides @@ -65,7 +71,7 @@ It is some basic mechanism which can be used to execute simple installation steps based on content described by the Open Component Model (see ocm bootstrap componentversions). -Therefore, a dedicated resource type ` + install.TypeTOIPackage + ` is defined. +Therefore, a dedicated resource type ` + toi.TypeTOIPackage + ` is defined. It is selected by a resource identity pattern. The first resource matching the pattern is used. A possible use case could be to provide different packages for different environments. The resource can use an identity attribute @@ -82,7 +88,7 @@ to perform the action. Finally, an executor is an image following the TOI specification for passing information into the image execution and receiving results from the execution. Such an image is described in two ways: - it either describes a resource of type ` + resourcetypes.OCI_IMAGE + ` or -- it describes a resource of type ` + install.TypeTOIExecutor + `, which defines +- it describes a resource of type ` + toi.TypeTOIExecutor + `, which defines the image to use and some default settings and further describes the features and requirements of the executor image. @@ -96,23 +102,27 @@ The execution of the container may do the needful to achieve the goal of the requested action and provide some labeled output files, which will be passed to the caller. -### The ` + install.TypeTOIPackage + ` Resource +### The ` + toi.TypeTOIPackage + ` Resource This resource describes an installable software package, whose content is contained in the component version, which contains the package resource. It is a plain yaml resource with the media types media type ` + mime.MIME_YAML + `, ` + mime.MIME_YAML_ALT + ` or -` + install.PackageSpecificationMimeType + `) containing +` + toi.PackageSpecificationMimeType + `) containing information required to control the instantiation of an executor. It has the following format: +- **description** (optional) *string* + + A short description of the installation package and some configuration hints. + - **executors** *[]ExecutorSpecification* - **configTemplate** (optional) *yaml* - This a [spiff](https://github.com/mandelsoft/spiff) template used to generate + This is a [spiff](https://github.com/mandelsoft/spiff) template used to generate The user config that is finally passed to the executor. If no template is specified the user parameter input will be processed directly without template. @@ -133,6 +143,17 @@ It has the following format: requites the specification of a credentials file providing the information how to satisfy those credential requests. +- **additionalResources** (optional) *map[string]ResourceReference* + + A set of additional resources specified by OCM resource references. + The key describes the meaning of the resource. The following keys have + a special meaning: + + - **configFile**: an example template for a parameter file + - **credentialsFile**: an example template for a credentials file + + Those templates can be downloaded with ocm bootstrap config. + #### *ExecutorSpecification* The executor specification describes the available actions and their mapping @@ -215,13 +236,13 @@ It always has at least one identity attribute name, which is the resource name field of the desired resource. If this resource defines additional identity attributes, the complete set must be specified. -### The ` + install.TypeTOIExecutor + ` Resource +### The ` + toi.TypeTOIExecutor + ` Resource Instead of directly describing an image resource i the package file, it is -possible to refer to a resource of type ` + install.TypeTOIExecutor + `. This +possible to refer to a resource of type ` + toi.TypeTOIExecutor + `. This is a yaml file with the media type ` + mime.MIME_YAML + `, ` + mime.MIME_YAML_ALT + ` or -` + install.PackageSpecificationMimeType + `) containing +` + toi.PackageSpecificationMimeType + `) containing common information about the executor executor. If used by the package, this information is used to validate settings in the package specification. diff --git a/components/demoplugin/Makefile b/components/demoplugin/Makefile index e4b9027cb..c91440493 100644 --- a/components/demoplugin/Makefile +++ b/components/demoplugin/Makefile @@ -14,8 +14,10 @@ GIT_TREE_STATE := $(shell [ -z "$$(git status -- CMDSRCS=$(shell find $(REPO_ROOT)/cmds/$(NAME) -type f) OCMSRCS=$(shell find $(REPO_ROOT)/pkg -type f) $(REPO_ROOT)/go.* +CREDS := $(shell $(REPO_ROOT)/hack/githubcreds.sh) +OCM = go run $(REPO_ROOT)/cmds/ocm $(CREDS) + GEN = $(REPO_ROOT)/gen/$(NAME) -OCM = go run $(REPO_ROOT)/cmds/ocm NOW := $(shell date -u +%FT%T%z) BUILD_FLAGS := "-s -w \ diff --git a/components/helmdemo/Makefile b/components/helmdemo/Makefile index 8500f0297..00a4281f8 100644 --- a/components/helmdemo/Makefile +++ b/components/helmdemo/Makefile @@ -12,8 +12,10 @@ VERSION = $(shell git describe --tags --e COMMIT = $(shell git rev-parse HEAD) EFFECTIVE_VERSION = $(VERSION)-$(COMMIT) -GEN = $(REPO_ROOT)/gen/$(NAME) -OCM = go run $(REPO_ROOT)/cmds/ocm +CREDS := $(shell $(REPO_ROOT)/hack/githubcreds.sh) +OCM = go run $(REPO_ROOT)/cmds/ocm $(CREDS) + +GEN := $(REPO_ROOT)/gen/$(NAME) CHARTSRCS=$(shell find echoserver -type f) @@ -21,7 +23,7 @@ CHARTSRCS=$(shell find echoserver -type f) ctf: $(GEN)/ctf $(GEN)/ctf: $(GEN)/ca - @rm -f $(GEN)/ctf + @rm -rf $(GEN)/ctf $(OCM) transfer ca $(GEN)/ca $(GEN)/ctf touch $(GEN)/ctf @@ -32,7 +34,7 @@ version: .PHONY: ca ca: $(GEN)/ca -$(GEN)/ca: $(GEN)/.exists sources.yaml resources.yaml references.yaml $(CHARTSRCS) packagespec.yaml helmconfig.yaml +$(GEN)/ca: $(GEN)/.exists sources.yaml resources.yaml references.yaml $(CHARTSRCS) packagespec.yaml examples/* helmconfig.yaml $(OCM) create ca -f $(COMPONENT) "$(VERSION)" --provider $(PROVIDER) --file $(GEN)/ca $(OCM) add sources $(GEN)/ca VERSION="$(VERSION)" COMMIT="$(COMMIT)" IMAGE="$(IMAGE):$(VERSION)" sources.yaml $(OCM) add resources $(GEN)/ca VERSION="$(VERSION)" COMMIT="$(COMMIT)" IMAGE="$(IMAGE):$(VERSION)" resources.yaml @@ -69,15 +71,16 @@ $(GEN)/.exists: info: @echo "ROOT: $(REPO_ROOT)" @echo "VERSION: $(VERSION)" - @echo "COMMIT; $(COMMIT)" + @echo "COMMIT: $(COMMIT)" + @echo "CREDS: $(CREDS)" .PHONY: describe describe: $(GEN)/ctf - ocm get resources --lookup $(OCMREPO) -c -o treewide $(GEN)/ctf + $(OCM) get resources --lookup $(OCMREPO) -c -o treewide $(GEN)/ctf .PHONY: descriptor descriptor: $(GEN)/ctf - ocm get component -S v3alpha1 -o yaml $(GEN)/ctf + $(OCM) get component -S v3alpha1 -o yaml $(GEN)/ctf .PHONY: clean clean: diff --git a/components/helmdemo/examples/config.yaml b/components/helmdemo/examples/config.yaml new file mode 100644 index 000000000..d7cbe366e --- /dev/null +++ b/components/helmdemo/examples/config.yaml @@ -0,0 +1 @@ +namespace: myechoserver \ No newline at end of file diff --git a/components/helmdemo/examples/creds.yaml b/components/helmdemo/examples/creds.yaml new file mode 100644 index 000000000..1182ea6fc --- /dev/null +++ b/components/helmdemo/examples/creds.yaml @@ -0,0 +1,4 @@ +credentials: + target: + credentials: + KUBECONFIG: (( read("~/k8s/CLUSTERS/test") )) \ No newline at end of file diff --git a/components/helmdemo/packagespec.yaml b/components/helmdemo/packagespec.yaml index aecc59c41..e6135f185 100644 --- a/components/helmdemo/packagespec.yaml +++ b/components/helmdemo/packagespec.yaml @@ -1,3 +1,6 @@ +description: | + This package describes the deployment of a simple echoserver + into a namespace of a Kubernetes cluster. executors: - resourceRef: resource: @@ -20,3 +23,10 @@ configScheme: type: string namespace: type: string +additionalResources: + configFile: + resource: + name: config-example + credentialsFile: + resource: + name: creds-example \ No newline at end of file diff --git a/components/helmdemo/references.yaml b/components/helmdemo/references.yaml index 56b7fc2b4..ced2e4a66 100644 --- a/components/helmdemo/references.yaml +++ b/components/helmdemo/references.yaml @@ -1,5 +1,5 @@ --- name: installer componentName: ${HELMINSTCOMP} -version: 0.1.0-dev +version: ${VERSION} diff --git a/components/helmdemo/resources.yaml b/components/helmdemo/resources.yaml index d51df3a74..c1b1674e0 100644 --- a/components/helmdemo/resources.yaml +++ b/components/helmdemo/resources.yaml @@ -21,3 +21,23 @@ version: "1.0" access: type: ociArtifact imageReference: gcr.io/google_containers/echoserver:1.10 +--- +name: config-example +type: yaml +labels: + - name: commit + value: ${COMMIT} +input: + type: file + mediaType: application/vnd.toi.ocm.software.config.v1+yaml + path: examples/config.yaml +--- +name: creds-example +type: yaml +labels: + - name: commit + value: ${COMMIT} +input: + type: file + mediaType: application/vnd.toi.ocm.software.credentials.v1+yaml + path: examples/creds.yaml diff --git a/components/helminstaller/Makefile b/components/helminstaller/Makefile index 81f20fd54..0677b1017 100644 --- a/components/helminstaller/Makefile +++ b/components/helminstaller/Makefile @@ -12,10 +12,12 @@ VERSION := $(shell git describe --tags -- 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) +PLATFORM := $(shell go env GOOS)/$(shell go env GOARCH) +CREDS := $(shell $(REPO_ROOT)/hack/githubcreds.sh) +OCM = go run $(REPO_ROOT)/cmds/ocm $(CREDS) GEN = $(REPO_ROOT)/gen/$(NAME) -OCM = go run $(REPO_ROOT)/cmds/ocm CMDSRCS=$(shell find $(REPO_ROOT)/cmds/$(NAME) -type f) OCMSRCS=$(shell find $(REPO_ROOT)/pkg -type f) $(REPO_ROOT)/go.* @@ -66,12 +68,16 @@ eval-resources: build: $(GEN)/image.$(NAME) $(GEN)/image.$(NAME): $(GEN)/.exists Dockerfile $(CMDSRCS) $(OCMSRCS) - docker build -t $(IMAGE):$(VERSION) --file Dockerfile $(REPO_ROOT) \ + docker buildx build -t $(IMAGE):$(VERSION) --platform $(PLATFORM) --file Dockerfile $(REPO_ROOT) \ --build-arg COMMIT=$(COMMIT) \ --build-arg EFFECTIVE_VERSION=$(EFFECTIVE_VERSION) \ --build-arg GIT_TREE_STATE=$(GIT_TREE_STATE) @touch $(GEN)/image.$(NAME) +push-image: + docker tag $(IMAGE):$(VERSION) $(OCMREPO)/$(COMPONENT)/$(NAME):$(VERSION) + docker push $(OCMREPO)/$(COMPONENT)/$(NAME):$(VERSION) + .PHONY: multi multi: $(GEN)/image.$(NAME).multi @@ -110,9 +116,11 @@ $(GEN)/.exists: .PHONY: info info: - @echo "ROOT: $(REPO_ROOT)" - @echo "VERSION: $(VERSION)" - @echo "COMMIT; $(COMMIT)" + @echo "ROOT: $(REPO_ROOT)" + @echo "VERSION: $(VERSION)" + @echo "COMMIT: $(COMMIT)" + @echo "GITHUBORG: $(GITHUBORG)" + @echo "PATFORM: $(PLATFORM)" .PHONY: describe describe: $(GEN)/ctf diff --git a/components/helminstaller/executorspec.yaml b/components/helminstaller/executorspec.yaml index 03ef82912..e9b0810a6 100644 --- a/components/helminstaller/executorspec.yaml +++ b/components/helminstaller/executorspec.yaml @@ -1,3 +1,4 @@ + actions: - install - uninstall @@ -5,18 +6,10 @@ imageRef: resource: name: toiimage configScheme: - type: object - additionalProperties: false - required: - - chart - - kubeConfigName - - imageMapping - properties: - chart: - additionalProperties: false + definitions: + resourceRef: type: object - required: - - resource + description: resource reference for charts properties: resource: type: object @@ -28,7 +21,20 @@ configScheme: type: object additionalProperties: type: string - + type: object + additionalProperties: false + required: + - chart + - kubeConfigName + - imageMapping + properties: + chart: + $ref: '#/definitions/resourceRef' + additionalProperties: false + subcharts: + type: object + additionalProperties: + $ref: '#/definitions/resourceRef' release: type: string createNamespace: @@ -64,4 +70,3 @@ configScheme: type: object kubeConfigName: type: string - diff --git a/components/ocmcli/Makefile b/components/ocmcli/Makefile index 2246f86a5..0ea2a59ef 100644 --- a/components/ocmcli/Makefile +++ b/components/ocmcli/Makefile @@ -15,8 +15,10 @@ EFFECTIVE_VERSION = $(VERSION)+$(COMMIT) CMDSRCS=$(shell find $(REPO_ROOT)/cmds/$(CMD) -type f) Makefile OCMSRCS=$(shell find $(REPO_ROOT)/pkg -type f) $(REPO_ROOT)/go.* +CREDS := $(shell $(REPO_ROOT)/hack/githubcreds.sh) +OCM = go run $(REPO_ROOT)/cmds/ocm $(CREDS) + GEN = $(REPO_ROOT)/gen/$(shell basename $(realpath .)) -OCM = go run $(REPO_ROOT)/cmds/ocm NOW := $(shell date -u +%FT%T%z) BUILD_FLAGS := "-s -w \ diff --git a/docs/reference/ocm.md b/docs/reference/ocm.md index bb8aeebb4..231836d88 100644 --- a/docs/reference/ocm.md +++ b/docs/reference/ocm.md @@ -15,8 +15,9 @@ ocm [] ... -h, --help help for ocm --logconfig string log config -L, --logfile string set log file + --logkeys stringArray log tags/realms(.) to be enabled ([.]name{,[.]name}[=level]) -l, --loglevel string set log level - -v, --verbose enable verbose logging + -v, --verbose deprecated: enable logrus verbose logging --version show version ``` @@ -72,8 +73,27 @@ form
-X <attribute>=<value>
+The --log* options can be used to configure the logging behaviour. +There is a quick config option --log-keys 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, which matches all logging realms +with the given realms as path prefix. A tag directly matches the logging tags. +Used tags and realms can be found under topic [ocm logging](ocm_logging.md). The ocm coding basically +uses the realm ocm. +The default level to enable is info. Separated by an equal sign (=) +optiobally a dedicated level can be specified. Log levels can be (error, +warn, info, debug and trace. +The default level is warn. + The value can be a simple type or a json string for complex values. The following attributes are supported: +- github.com/mandelsoft/logforward: *logconfig* Logging config structure used for config forwarding + + THis attribute is used to specify a logging configuration intended + to be forwarded to other tool. + (For example: TOI passes this config to the executor) + - github.com/mandelsoft/oci/cache [cache]: *string* Filesystem folder to use for caching OCI blobs @@ -157,6 +177,7 @@ attributes are supported: * [ocm attributes](ocm_attributes.md) — configuration attributes used to control the behaviour * [ocm configfile](ocm_configfile.md) — configuration file +* [ocm logging](ocm_logging.md) — Configured logging keys * [ocm oci-references](ocm_oci-references.md) — notation for OCI references * [ocm ocm-accessmethods](ocm_ocm-accessmethods.md) — List of all supported access methods * [ocm ocm-references](ocm_ocm-references.md) — notation for OCM references diff --git a/docs/reference/ocm_add_componentversions.md b/docs/reference/ocm_add_componentversions.md index f82bba482..ddd3896bc 100644 --- a/docs/reference/ocm_add_componentversions.md +++ b/docs/reference/ocm_add_componentversions.md @@ -10,11 +10,14 @@ ocm add componentversions [] [--version ] [] {--create is given, the archive is created first. An additional option --force will recreate an empty archive if it already exists. +If option --complete is given all component versions referenced by +the added one, will be added, also. Therefore, the --lookup is required +to specify an OCM repository to lookup the missing component versions. If +additionally the -V is given, the resources of those additional +components will be added by value. + The source, resource and reference list can be composed according the commands [ocm add sources](ocm_add_sources.md), [ocm add resources](ocm_add_resources.md), [ocm add references](ocm_add_references.md), respectively. @@ -95,6 +104,22 @@ There are several templaters that can be selected by the --templater +If a component lookup for building a reference closure is required +the --lookup option can be used to specify a fallback +lookup repository. +By default the component versions are searched in the repository +holding the component version for which the closure is determined. +For *Component Archives* this is never possible, because it only +contains a single component version. Therefore, in this scenario +this option must always be specified to be able to follow component +references. + +It the option --copy-resources is given, all referential +resources will potentially be localized, mapped to component version local +resources in the target repository. +This behaviour can be further influenced by specifying a transfer script +with the script option family. + ### Examples diff --git a/docs/reference/ocm_add_source-configuration.md b/docs/reference/ocm_add_source-configuration.md index 4daacc5d6..81abf52cf 100644 --- a/docs/reference/ocm_add_source-configuration.md +++ b/docs/reference/ocm_add_source-configuration.md @@ -96,7 +96,7 @@ or input fields of the description file format. Elements must follow the resource meta data description scheme of the component descriptor. -If not specified anywhere the artifact type will be defaulted to filesystem. +If not specified anywhere the artifact type will be defaulted to directoryTree. If expressions/templates are used in the specification file an appropriate templater and the required settings might be required to provide diff --git a/docs/reference/ocm_attributes.md b/docs/reference/ocm_attributes.md index 5bd28a760..06d83c739 100644 --- a/docs/reference/ocm_attributes.md +++ b/docs/reference/ocm_attributes.md @@ -10,6 +10,12 @@ command line options of the main command (see [ocm](ocm.md)). The following options are available in the currently used version of the OCM library: +- github.com/mandelsoft/logforward: *logconfig* Logging config structure used for config forwarding + + THis attribute is used to specify a logging configuration intended + to be forwarded to other tool. + (For example: TOI passes this config to the executor) + - github.com/mandelsoft/oci/cache [cache]: *string* Filesystem folder to use for caching OCI blobs diff --git a/docs/reference/ocm_bootstrap.md b/docs/reference/ocm_bootstrap.md index 803f78a04..14ed4fb87 100644 --- a/docs/reference/ocm_bootstrap.md +++ b/docs/reference/ocm_bootstrap.md @@ -21,5 +21,6 @@ ocm bootstrap [] ... ##### Sub Commands -* [ocm bootstrap componentversions](ocm_bootstrap_componentversions.md) — bootstrap component version +* [ocm bootstrap configuration](ocm_bootstrap_configuration.md) — bootstrap TOI configuration files +* [ocm bootstrap package](ocm_bootstrap_package.md) — bootstrap component version diff --git a/docs/reference/ocm_bootstrap_componentversions.md b/docs/reference/ocm_bootstrap_configuration.md similarity index 64% rename from docs/reference/ocm_bootstrap_componentversions.md rename to docs/reference/ocm_bootstrap_configuration.md index fa0526ef8..03636463e 100644 --- a/docs/reference/ocm_bootstrap_componentversions.md +++ b/docs/reference/ocm_bootstrap_configuration.md @@ -1,49 +1,40 @@ -## ocm bootstrap componentversions — Bootstrap Component Version +## ocm bootstrap configuration — Bootstrap TOI Configuration Files ### Synopsis ``` -ocm bootstrap componentversions [] {} {} +ocm bootstrap configuration [] {} {} ``` ### Options ``` - -c, --credentials string credentials file - -h, --help help for componentversions - -o, --outputs string output file/directory - -p, --parameters string parameter file + -c, --credentials string credentials file name (default "TOICredentials") + -h, --help help for configuration + --lookup stringArray repository name or spec for closure lookup fallback + -p, --parameters string parameter file name (default "TOIParameters") + --repo string repository name or spec ``` ### Description -Use the simple TOI bootstrap mechanism to execute actions for a TOI package resource -based on the content of an OCM component version and some command input describing -the dedicated installation target. +If a TOI package provides information for configuration file templates/prototypes +this command extracts this data and provides appropriate files in the filesystem. The package resource must have the type toiPackage. This is a simple YAML file resource describing the bootstrapping of a dedicated kind of software. See also the topic [ocm toi toi-bootstrapping](ocm_toi_toi-bootstrapping.md). -THis resource finally describes an executor image, which will be executed in a -container with the installation source and (instance specific) user settings. -The container is just executed, the framework make no assumption about the -meaning/outcome of the execution. Therefore, any kind of actions can be described and -issued this way, not on installation handling. - The first matching resource of this type is selected. Optionally a set of identity attribute can be specified used to refine the match. This can be the resource name and/or other key/value pairs (<attr>=<value>). -If no output file is provided, the yaml representation of the outputs are -printed to standard out. If the output file is a directory, for every output a -dedicated file is created, otherwise the yaml representation is stored to the -file. - If no credentials file name is provided (option -c) the file -TOICredentials is used, if present. If no parameter file name is -provided (option -p) the file TOIParameters is used, if present. +TOICredentials is used. If no parameter file name is +provided (option -p) the file TOIParameters is used. + +For more details about those files see [ ocm bootstrap package](_ocm_bootstrap_package.md). If the --repo option is specified, the given names are interpreted relative to the specified repository using the syntax @@ -104,7 +95,7 @@ references. ### Examples ``` -$ ocm toi bootstrap componentversion ghcr.io/mandelsoft/ocmdemoinstaller:0.0.1-dev +$ ocm toi bootstrap config ghcr.io/mandelsoft/ocm//ocmdemoinstaller:0.0.1-dev ``` ### SEE ALSO @@ -118,10 +109,11 @@ $ ocm toi bootstrap componentversion ghcr.io/mandelsoft/ocmdemoinstaller:0.0.1-d ##### Additional Help Topics -* [ocm bootstrap componentversions toi-bootstrapping](ocm_bootstrap_componentversions_toi-bootstrapping.md) — Tiny OCM Installer based on component versions +* [ocm bootstrap configuration toi-bootstrapping](ocm_bootstrap_configuration_toi-bootstrapping.md) — Tiny OCM Installer based on component versions ##### Additional Links * [ocm toi toi-bootstrapping](ocm_toi_toi-bootstrapping.md) +* [ ocm bootstrap package](_ocm_bootstrap_package.md) diff --git a/docs/reference/ocm_bootstrap_configuration_toi-bootstrapping.md b/docs/reference/ocm_bootstrap_configuration_toi-bootstrapping.md new file mode 100644 index 000000000..09b69ccdb --- /dev/null +++ b/docs/reference/ocm_bootstrap_configuration_toi-bootstrapping.md @@ -0,0 +1,383 @@ +## ocm bootstrap configuration toi-bootstrapping — Tiny OCM Installer Based On Component Versions + +### Description + + +TOI is a small toolset on top of the Open Component Model. It provides +a possibility to run images taken from a component version with user +configuration and feed them with the content of this component version. +It is some basic mechanism which can be used to execute simple installation +steps based on content described by the Open Component Model +(see [ocm bootstrap componentversions](ocm_bootstrap_componentversions.md)). + +Therefore, a dedicated resource type toiPackage is defined. +It is selected by a resource identity pattern. The first resource matching the pattern +is used. A possible use case could be to provide different packages for +different environments. The resource can use an identity attribute +platform=<value>. By specifying just the platform attribute, +the appropriate package will be chosen. + +The bootstrap command uses this resource to determine a TOI executor together +executor configuration and additional client specific settings to describe +a dedicated installation. + +To do this the package describes dedicated actions that can be executed by +the bootstrap command. Every action refers to an executor, which is executed +to perform the action. Finally, an executor is an image following the TOI +specification for passing information into the image execution and receiving +results from the execution. Such an image is described in two ways: +- it either describes a resource of type ociImage or +- it describes a resource of type toiExecutor, which defines + the image to use and some default settings and further describes the features + and requirements of the executor image. + +The package described credentials requirements and required user configuration +which must passed along with the bootstrap command. After validation of the +input finally a container with the selected executor image is created, that +contains the content of the initial component version in form of a Common +Transport Archive and all the specified configuration data. + +The execution of the container may do the needful to achieve the goal of the +requested action and provide some labeled output files, which will be passed +to the caller. + +### The toiPackage Resource + +This resource describes an installable software package, whose content is +contained in the component version, which contains the package resource. + +It is a plain yaml resource with the media types media type application/x-yaml, +text/yaml or +application/vnd.toi.ocm.software.package.v1+yaml) containing +information required to control the instantiation of an executor. + +It has the following format: + +- **description** (optional) *string* + + A short description of the installation package and some configuration hints. + +- **executors** *[]ExecutorSpecification* + +- **configTemplate** (optional) *yaml* + + This is a [spiff](https://github.com/mandelsoft/spiff) template used to generate + The user config that is finally passed to the executor. If no template + is specified the user parameter input will be processed directly without template. + +- **configScheme** (optional) *yaml* + + This is a [JSONSCHEMA](https://json-schema.org/) used to validate the user + input prior to merging with the template + +- **templateLibraries** (optional) *[]ResourceReference* + + This is a list of resources whose content is used as additional stubs + for the template processing. + +- **credentials** (optional) *map[string]CredentialRequest* + + Here the package may request the provisioning of some credentials with a + dedicated name/purpose and structure. If specified the bootstrap command + requites the specification of a credentials file providing the information + how to satisfy those credential requests. + +- **additionalResources** (optional) *map[string]ResourceReference* + + A set of additional resources specified by OCM resource references. + The key describes the meaning of the resource. The following keys have + a special meaning: + + - **configFile**: an example template for a parameter file + - **credentialsFile**: an example template for a credentials file + + Those templates can be downloaded with [ocm bootstrap config](ocm_bootstrap_config.md). + +#### *ExecutorSpecification* + +The executor specification describes the available actions and their mapping +to executors. It uses the following fields: + +- **actions** *[]string* + + The list of actions this executor can be used for. If nothings is specified + the executor will be used for all actions. The first matching executor entry + will be used to execute an action by the bootstrap command + +- **resourceRef** *[]ResourceReference* + + An OCM resource reference describing a component version resource relative to + the component version containing the package resource. + +- **config** (optional) *yaml* + + This is optional static executor config passed to the executor as is. It is + to describe the set of elements on which the actual execution of the executor + should work on. + +- **parameterMapping** (optional) *spiff yaml* + + This is an optional spiff template used to process the actual parameter set + passed by the caller to transform it to the requirements of the actual executor. + + A package has global parameter setting, but possibly multiple different + executors for different action. They might have different requirements/formats + concerning the parameter input. There the executor specification allows to + map the provided user input, accordingly + +- **credentialMapping** (optional) *map[string]string* + + This is an optional mapping to map credential names used by the package + to the needs of dedicated executors. + + A package has global parameter setting, but possibly multiple different + executors for different action. They might have different requirements/formats + concerning the parameter input. There the executor specification allows to + map the provided user input, accordingly + +- **image** (development) *object* + + Instead of a resourceRef it is possible to directly specify an + absolute image. + + ATTENTION: this is intended for development purposes, ONLY. Do not use it + for final component versions. + + It has the field ref and the optional field digest. + +- **outputs** (optional) *map[string]string* + + This field can be used to map the names of outputs provided by a dedicated + executor outputs to package outputs. + +#### *ResourceReference* + +An OCM resource reference describes a resource of a component version. It is +always evaluated relative to the component version providing the resource +that contains the resource reference. It uses the following fields: + +- **resourcePath** (optional) *[]Identity* + + This is sequence of reference identities used to follow a chain of + component version references starting with the actual component version. + If not specified the specified resource will be taken from the actual + component version. + +- **resource** *Identity* + + This is the identity of the resource in the selected component version. + +#### *Identity* + +An identity specification is a map[string]string. It describes +the identity attributes of a desired resource in a component version. +It always has at least one identity attribute name, which +is the resource name field of the desired resource. If this resource +defines additional identity attributes, the complete set must be specified. + +### The toiExecutor Resource + +Instead of directly describing an image resource i the package file, it is +possible to refer to a resource of type toiExecutor. This +is a yaml file with the media type application/x-yaml, +text/yaml or +application/vnd.toi.ocm.software.package.v1+yaml) containing +common information about the executor executor. If used by the package, +this information is used to validate settings in the package specification. + +It has the following format: + +- **imageRef** *ResourceReference* + + This field reference the image resource relative to the component version + providing the executor resource + +- **configTemplate** (optional) *yaml* + + This a [spiff](https://github.com/mandelsoft/spiff) template used to generate + The executor config from the package specification that is finally passed to + the executor. If no template is specified the executor config specified in + the package will be processed directly without template. + +- **configScheme** (optional) *yaml* + + This is a [JSONSCHEMA](https://json-schema.org/) used to validate the executor + config from the package prior to merging with the template + +- **templateLibraries** (optional) *[]ResourceReference* + + This is a list of resources whose content is used as additional stubs + for the template processing. + +- **credentials** (optional) *map[string]CredentialRequest* + + Here the executor may request the provisioning of some credentials with a + dedicated name/purpose and structure. If specified it will be propagated + to a using package. It this uses an own credentials section, this one + will be filtered and checked for the actual executor. + +- **outputs** (optional) *map[string]OutputSpecification* + + This field can be used to describe the provided outputs of this executor. + The *OutputSpecification* contains only the field description, + so far. It is intended to be extended to contain further information to more + formally describe the type of output. + +- **image** (development) *object* + + Instead of an imageRef it is possible to directly specify an + absolute image. + + ATTENTION: this is intended for development purposes, ONLY. Do not use it + for final component versions. + + It has the field ref and the optional field digest. + +### Client Parameters + +Common to all executors a parameter file can be provided by the caller. The +package specification may provide a [spiff template](https://github.com/mandelsoft/spiff) +for this parameter file. It can be used, for example to provide useful defaults. +The actually provided content is merged with this template. + +To validate user configuration a JSON scheme can be provided. The user input is +validated first against this scheme before the actual merge is done. + +### Credentials + +Additionally credentials can be requested to be provided by a client. +This is done with the credentials field. It is a map +of credentials names and their meaning and/or handling. + +It uses the following fields: + +- **description** *string* + + This field should describe the purpose of the credential. + +- **properties** *map[string]string* + + This field should describe the used credential fields + +- **consumerId** *map[string]* + + This field can be used to optionally define a consumer id that should be set + in the OCM support library, if used by the executor. At least the field + type and one additional field must be set. + +Credentials are provided in an ocm config file (see [ocm configfile](ocm_configfile.md)). +It uses a memory credential repository with the name default +to store the credentials under the given name. Additionally appropriate +consumer ids will be propagated, if requested in the credentials request config. + +### Executor Image Contract + +The executor image is called with the action as additional argument. It is +expected that is defines a default entry point and a potentially empty list of +standard arguments. + +It is called with two arguments: +- name of the action to execute +- identity of the component version containing the package the executor + is executed for. + + This can be used to access the component descriptor to get access to + further described resources in the executor config + +The container used to execute the executor image gets prepared a standard +filesystem structure used to provide all the executor inputs before the +execution and reading provided executor outputs after the execution. + +
+/
+└── toi
+    ├── inputs
+    │   ├── config      configuration from package specification
+    │   ├── ocmrepo     OCM filesystem repository containing the complete
+    │   │               component version of the package
+    │   └── parameters  merged complete parameter file
+    ├── outputs
+    │   ├── <out>       any number of arbitrary output data provided
+    │   │               by executor
+    │   └── ...         
+    └── run             good practice: typical location for the executed command
+
+ +After processing it is possible to return named outputs. The name of an output +must be a filename. The executor section in the package specification maps those +files to logical outputs in the outputs section. + +
+ <file name by executor> -> <logical output name> +
+ +Basically the output may contain any data, but is strongly recommended +to use yaml or json files, only. This enables further formal processing +by the TOI toolset. + + +### Examples + +``` +description: | + This package is just an example. +executors: + - actions: + - install + resourceRef: + resource: + name: installerimage + config: + level: info +# parameterMapping: # optional spiff mapping of Package configuration to +# .... # executor parameters + outputs: + test: bla +credentials: + target: + description: kubeconfig for target kubernetes cluster + consumerId: + type: Kubernetes + purpose: target +configTemplate: + parameters: + username: admin + password: (( &merge )) +configScheme: + type: object + required: + - parameters + additionalProperties: false + properties: + parameters: + type: object + required: + - password + additionalProperties: false + properties: + username: + type: string + password: + type: string +additionalResources: + configFile: + resource: + name: config-file +``` + +### SEE ALSO + +##### Parents + +* [ocm bootstrap configuration](ocm_bootstrap_configuration.md) — bootstrap TOI configuration files +* [ocm bootstrap](ocm_bootstrap.md) — bootstrap components +* [ocm](ocm.md) — Open Component Model command line client + + + +##### Additional Links + +* [ocm bootstrap componentversions](ocm_bootstrap_componentversions.md) +* [ocm bootstrap config](ocm_bootstrap_config.md) +* [ocm configfile](ocm_configfile.md) — configuration file + diff --git a/docs/reference/ocm_bootstrap_package.md b/docs/reference/ocm_bootstrap_package.md new file mode 100644 index 000000000..db32aba84 --- /dev/null +++ b/docs/reference/ocm_bootstrap_package.md @@ -0,0 +1,179 @@ +## ocm bootstrap package — Bootstrap Component Version + +### Synopsis + +``` +ocm bootstrap package [] {} {} +``` + +### Options + +``` + -c, --credentials string credentials file + -h, --help help for package + --lookup stringArray repository name or spec for closure lookup fallback + -o, --outputs string output file/directory + -p, --parameters string parameter file + --repo string repository name or spec +``` + +### Description + + +Use the simple TOI bootstrap mechanism to execute actions for a TOI package resource +based on the content of an OCM component version and some command input describing +the dedicated installation target. + +The package resource must have the type toiPackage. +This is a simple YAML file resource describing the bootstrapping of a dedicated kind +of software. See also the topic [ocm toi toi-bootstrapping](ocm_toi_toi-bootstrapping.md). + +This resource finally describes an executor image, which will be executed in a +container with the installation source and (instance specific) user settings. +The container is just executed, the framework make no assumption about the +meaning/outcome of the execution. Therefore, any kind of actions can be described and +issued this way, not on installation handling. + +The first matching resource of this type is selected. Optionally a set of +identity attribute can be specified used to refine the match. This can be the +resource name and/or other key/value pairs (<attr>=<value>). + +If no output file is provided, the yaml representation of the outputs are +printed to standard out. If the output file is a directory, for every output a +dedicated file is created, otherwise the yaml representation is stored to the +file. + +If no credentials file name is provided (option -c) the file +TOICredentials is used, if present. If no parameter file name is +provided (option -p) the file TOIParameters is used, if present. + +Using the credentials file it is possible to configure credentials required by +the installation package or executor. Additionally arbitrary consumer ids +can be forwarded to executor, which might be required by accessing blobs +described by external access methods. + +The credentials file uses the following yaml format: +- credentials *map[string]CredentialsSpec* + + The resolution of credentials requested by the package (by name). + +- forwardedConsumers *[]ForwardSpec* (optional) + + An optional list of consumer specifications to be forwarded to the OCM + configuration provided to the executor. + +The *CredentialsSpec* uses the following format: + +- consumerId *map[string]string* + + The consumer id used to look up the credentials. + +- consumerType *string* (optional) (default: partial) + + The type of the matcher used to match the consumer id. + +- reference *yaml* + + A generic credential specification as used in the ocm config file. + +- credentials *map[string]string* + + Direct credential fields. + +One of consumerId, reference or credentials +must be configured. + +The *ForwardSpec* uses the following format: + +- consumerId *map[string]string* + + The consumer id to be forwarded. + +- consumerType *string* (optional) (default: partial) + + The type of the matcher used to match the consumer id. + +If provided by the package it is possible to download template versions +for the parameter and credentials file using the command [ocm bootstrap configuration](ocm_bootstrap_configuration.md). + +If the --repo option is specified, the given names are interpreted +relative to the specified repository using the syntax + +
+
<component>[:<version>]
+
+ +If no --repo option is specified the given names are interpreted +as located OCM component version references: + +
+
[<repo type>::]<host>[:<port>][/<base path>]//<component>[:<version>]
+
+ +Additionally there is a variant to denote common transport archives +and general repository specifications + +
+
[<repo type>::]<filepath>|<spec json>[//<component>[:<version>]]
+
+ +The --repo option takes an OCM repository specification: + +
+
[<repo type>::]<configured name>|<file path>|<spec json>
+
+ +For the *Common Transport Format* the types directory, +tar or tgz is possible. + +Using the JSON variant any repository type supported by the +linked library can be used: + +Dedicated OCM repository types: +- `ComponentArchive` + +OCI Repository types (using standard component repository to OCI mapping): +- `ArtifactSet` +- `CommonTransportFormat` +- `DockerDaemon` +- `Empty` +- `OCIRegistry` +- `oci` +- `ociRegistry` + +If a component lookup for building a reference closure is required +the --lookup option can be used to specify a fallback +lookup repository. +By default the component versions are searched in the repository +holding the component version for which the closure is determined. +For *Component Archives* this is never possible, because it only +contains a single component version. Therefore, in this scenario +this option must always be specified to be able to follow component +references. + + +### Examples + +``` +$ ocm toi bootstrap package ghcr.io/mandelsoft/ocm//ocmdemoinstaller:0.0.1-dev +``` + +### SEE ALSO + +##### Parents + +* [ocm bootstrap](ocm_bootstrap.md) — bootstrap components +* [ocm](ocm.md) — Open Component Model command line client + + + +##### Additional Help Topics + +* [ocm bootstrap package toi-bootstrapping](ocm_bootstrap_package_toi-bootstrapping.md) — Tiny OCM Installer based on component versions + + +##### Additional Links + +* [ocm toi toi-bootstrapping](ocm_toi_toi-bootstrapping.md) +* [ocm bootstrap configuration](ocm_bootstrap_configuration.md) — bootstrap TOI configuration files + diff --git a/docs/reference/ocm_bootstrap_componentversions_toi-bootstrapping.md b/docs/reference/ocm_bootstrap_package_toi-bootstrapping.md similarity index 92% rename from docs/reference/ocm_bootstrap_componentversions_toi-bootstrapping.md rename to docs/reference/ocm_bootstrap_package_toi-bootstrapping.md index d5f60ee94..c84bf68b2 100644 --- a/docs/reference/ocm_bootstrap_componentversions_toi-bootstrapping.md +++ b/docs/reference/ocm_bootstrap_package_toi-bootstrapping.md @@ -1,4 +1,4 @@ -## ocm bootstrap componentversions toi-bootstrapping — Tiny OCM Installer Based On Component Versions +## ocm bootstrap package toi-bootstrapping — Tiny OCM Installer Based On Component Versions ### Description @@ -53,11 +53,15 @@ information required to control the instantiation of an executor. It has the following format: +- **description** (optional) *string* + + A short description of the installation package and some configuration hints. + - **executors** *[]ExecutorSpecification* - **configTemplate** (optional) *yaml* - This a [spiff](https://github.com/mandelsoft/spiff) template used to generate + This is a [spiff](https://github.com/mandelsoft/spiff) template used to generate The user config that is finally passed to the executor. If no template is specified the user parameter input will be processed directly without template. @@ -78,6 +82,17 @@ It has the following format: requites the specification of a credentials file providing the information how to satisfy those credential requests. +- **additionalResources** (optional) *map[string]ResourceReference* + + A set of additional resources specified by OCM resource references. + The key describes the meaning of the resource. The following keys have + a special meaning: + + - **configFile**: an example template for a parameter file + - **credentialsFile**: an example template for a credentials file + + Those templates can be downloaded with [ocm bootstrap config](ocm_bootstrap_config.md). + #### *ExecutorSpecification* The executor specification describes the available actions and their mapping @@ -304,6 +319,8 @@ by the TOI toolset. ### Examples ``` +description: | + This package is just an example. executors: - actions: - install @@ -342,13 +359,17 @@ configScheme: type: string password: type: string +additionalResources: + configFile: + resource: + name: config-file ``` ### SEE ALSO ##### Parents -* [ocm bootstrap componentversions](ocm_bootstrap_componentversions.md) — bootstrap component version +* [ocm bootstrap package](ocm_bootstrap_package.md) — bootstrap component version * [ocm bootstrap](ocm_bootstrap.md) — bootstrap components * [ocm](ocm.md) — Open Component Model command line client @@ -356,5 +377,7 @@ configScheme: ##### Additional Links +* [ocm bootstrap componentversions](ocm_bootstrap_componentversions.md) +* [ocm bootstrap config](ocm_bootstrap_config.md) * [ocm configfile](ocm_configfile.md) — configuration file diff --git a/docs/reference/ocm_describe.md b/docs/reference/ocm_describe.md index 11de60e71..527ba3bcd 100644 --- a/docs/reference/ocm_describe.md +++ b/docs/reference/ocm_describe.md @@ -22,5 +22,6 @@ ocm describe [] ... ##### Sub Commands * [ocm describe artifacts](ocm_describe_artifacts.md) — describe artifact version +* [ocm describe package](ocm_describe_package.md) — describe TOI package * [ocm describe plugins](ocm_describe_plugins.md) — get plugins diff --git a/docs/reference/ocm_describe_package.md b/docs/reference/ocm_describe_package.md new file mode 100644 index 000000000..e93e18791 --- /dev/null +++ b/docs/reference/ocm_describe_package.md @@ -0,0 +1,109 @@ +## ocm describe package — Describe TOI Package + +### Synopsis + +``` +ocm describe package [] {} {} +``` + +### Options + +``` + -h, --help help for package + --lookup stringArray repository name or spec for closure lookup fallback + --repo string repository name or spec +``` + +### Description + + +Describe a TOI package provided by a resource of an OCM component version. + +The package resource must have the type toiPackage. +This is a simple YAML file resource describing the bootstrapping of a dedicated kind +of software. See also the topic [ocm toi toi-bootstrapping](ocm_toi_toi-bootstrapping.md). + +The first matching resource of this type is selected. Optionally a set of +identity attribute can be specified used to refine the match. This can be the +resource name and/or other key/value pairs (<attr>=<value>). + +If the --repo option is specified, the given names are interpreted +relative to the specified repository using the syntax + +
+
<component>[:<version>]
+
+ +If no --repo option is specified the given names are interpreted +as located OCM component version references: + +
+
[<repo type>::]<host>[:<port>][/<base path>]//<component>[:<version>]
+
+ +Additionally there is a variant to denote common transport archives +and general repository specifications + +
+
[<repo type>::]<filepath>|<spec json>[//<component>[:<version>]]
+
+ +The --repo option takes an OCM repository specification: + +
+
[<repo type>::]<configured name>|<file path>|<spec json>
+
+ +For the *Common Transport Format* the types directory, +tar or tgz is possible. + +Using the JSON variant any repository type supported by the +linked library can be used: + +Dedicated OCM repository types: +- `ComponentArchive` + +OCI Repository types (using standard component repository to OCI mapping): +- `ArtifactSet` +- `CommonTransportFormat` +- `DockerDaemon` +- `Empty` +- `OCIRegistry` +- `oci` +- `ociRegistry` + +If a component lookup for building a reference closure is required +the --lookup option can be used to specify a fallback +lookup repository. +By default the component versions are searched in the repository +holding the component version for which the closure is determined. +For *Component Archives* this is never possible, because it only +contains a single component version. Therefore, in this scenario +this option must always be specified to be able to follow component +references. + + +### Examples + +``` +$ ocm toi describe package ghcr.io/mandelsoft/ocm//ocmdemoinstaller:0.0.1-dev +``` + +### SEE ALSO + +##### Parents + +* [ocm describe](ocm_describe.md) — Describe various elements by using appropriate sub commands. +* [ocm](ocm.md) — Open Component Model command line client + + + +##### Additional Help Topics + +* [ocm describe package toi-bootstrapping](ocm_describe_package_toi-bootstrapping.md) — Tiny OCM Installer based on component versions + + +##### Additional Links + +* [ocm toi toi-bootstrapping](ocm_toi_toi-bootstrapping.md) + diff --git a/docs/reference/ocm_describe_package_toi-bootstrapping.md b/docs/reference/ocm_describe_package_toi-bootstrapping.md new file mode 100644 index 000000000..2e038fffa --- /dev/null +++ b/docs/reference/ocm_describe_package_toi-bootstrapping.md @@ -0,0 +1,383 @@ +## ocm describe package toi-bootstrapping — Tiny OCM Installer Based On Component Versions + +### Description + + +TOI is a small toolset on top of the Open Component Model. It provides +a possibility to run images taken from a component version with user +configuration and feed them with the content of this component version. +It is some basic mechanism which can be used to execute simple installation +steps based on content described by the Open Component Model +(see [ocm bootstrap componentversions](ocm_bootstrap_componentversions.md)). + +Therefore, a dedicated resource type toiPackage is defined. +It is selected by a resource identity pattern. The first resource matching the pattern +is used. A possible use case could be to provide different packages for +different environments. The resource can use an identity attribute +platform=<value>. By specifying just the platform attribute, +the appropriate package will be chosen. + +The bootstrap command uses this resource to determine a TOI executor together +executor configuration and additional client specific settings to describe +a dedicated installation. + +To do this the package describes dedicated actions that can be executed by +the bootstrap command. Every action refers to an executor, which is executed +to perform the action. Finally, an executor is an image following the TOI +specification for passing information into the image execution and receiving +results from the execution. Such an image is described in two ways: +- it either describes a resource of type ociImage or +- it describes a resource of type toiExecutor, which defines + the image to use and some default settings and further describes the features + and requirements of the executor image. + +The package described credentials requirements and required user configuration +which must passed along with the bootstrap command. After validation of the +input finally a container with the selected executor image is created, that +contains the content of the initial component version in form of a Common +Transport Archive and all the specified configuration data. + +The execution of the container may do the needful to achieve the goal of the +requested action and provide some labeled output files, which will be passed +to the caller. + +### The toiPackage Resource + +This resource describes an installable software package, whose content is +contained in the component version, which contains the package resource. + +It is a plain yaml resource with the media types media type application/x-yaml, +text/yaml or +application/vnd.toi.ocm.software.package.v1+yaml) containing +information required to control the instantiation of an executor. + +It has the following format: + +- **description** (optional) *string* + + A short description of the installation package and some configuration hints. + +- **executors** *[]ExecutorSpecification* + +- **configTemplate** (optional) *yaml* + + This is a [spiff](https://github.com/mandelsoft/spiff) template used to generate + The user config that is finally passed to the executor. If no template + is specified the user parameter input will be processed directly without template. + +- **configScheme** (optional) *yaml* + + This is a [JSONSCHEMA](https://json-schema.org/) used to validate the user + input prior to merging with the template + +- **templateLibraries** (optional) *[]ResourceReference* + + This is a list of resources whose content is used as additional stubs + for the template processing. + +- **credentials** (optional) *map[string]CredentialRequest* + + Here the package may request the provisioning of some credentials with a + dedicated name/purpose and structure. If specified the bootstrap command + requites the specification of a credentials file providing the information + how to satisfy those credential requests. + +- **additionalResources** (optional) *map[string]ResourceReference* + + A set of additional resources specified by OCM resource references. + The key describes the meaning of the resource. The following keys have + a special meaning: + + - **configFile**: an example template for a parameter file + - **credentialsFile**: an example template for a credentials file + + Those templates can be downloaded with [ocm bootstrap config](ocm_bootstrap_config.md). + +#### *ExecutorSpecification* + +The executor specification describes the available actions and their mapping +to executors. It uses the following fields: + +- **actions** *[]string* + + The list of actions this executor can be used for. If nothings is specified + the executor will be used for all actions. The first matching executor entry + will be used to execute an action by the bootstrap command + +- **resourceRef** *[]ResourceReference* + + An OCM resource reference describing a component version resource relative to + the component version containing the package resource. + +- **config** (optional) *yaml* + + This is optional static executor config passed to the executor as is. It is + to describe the set of elements on which the actual execution of the executor + should work on. + +- **parameterMapping** (optional) *spiff yaml* + + This is an optional spiff template used to process the actual parameter set + passed by the caller to transform it to the requirements of the actual executor. + + A package has global parameter setting, but possibly multiple different + executors for different action. They might have different requirements/formats + concerning the parameter input. There the executor specification allows to + map the provided user input, accordingly + +- **credentialMapping** (optional) *map[string]string* + + This is an optional mapping to map credential names used by the package + to the needs of dedicated executors. + + A package has global parameter setting, but possibly multiple different + executors for different action. They might have different requirements/formats + concerning the parameter input. There the executor specification allows to + map the provided user input, accordingly + +- **image** (development) *object* + + Instead of a resourceRef it is possible to directly specify an + absolute image. + + ATTENTION: this is intended for development purposes, ONLY. Do not use it + for final component versions. + + It has the field ref and the optional field digest. + +- **outputs** (optional) *map[string]string* + + This field can be used to map the names of outputs provided by a dedicated + executor outputs to package outputs. + +#### *ResourceReference* + +An OCM resource reference describes a resource of a component version. It is +always evaluated relative to the component version providing the resource +that contains the resource reference. It uses the following fields: + +- **resourcePath** (optional) *[]Identity* + + This is sequence of reference identities used to follow a chain of + component version references starting with the actual component version. + If not specified the specified resource will be taken from the actual + component version. + +- **resource** *Identity* + + This is the identity of the resource in the selected component version. + +#### *Identity* + +An identity specification is a map[string]string. It describes +the identity attributes of a desired resource in a component version. +It always has at least one identity attribute name, which +is the resource name field of the desired resource. If this resource +defines additional identity attributes, the complete set must be specified. + +### The toiExecutor Resource + +Instead of directly describing an image resource i the package file, it is +possible to refer to a resource of type toiExecutor. This +is a yaml file with the media type application/x-yaml, +text/yaml or +application/vnd.toi.ocm.software.package.v1+yaml) containing +common information about the executor executor. If used by the package, +this information is used to validate settings in the package specification. + +It has the following format: + +- **imageRef** *ResourceReference* + + This field reference the image resource relative to the component version + providing the executor resource + +- **configTemplate** (optional) *yaml* + + This a [spiff](https://github.com/mandelsoft/spiff) template used to generate + The executor config from the package specification that is finally passed to + the executor. If no template is specified the executor config specified in + the package will be processed directly without template. + +- **configScheme** (optional) *yaml* + + This is a [JSONSCHEMA](https://json-schema.org/) used to validate the executor + config from the package prior to merging with the template + +- **templateLibraries** (optional) *[]ResourceReference* + + This is a list of resources whose content is used as additional stubs + for the template processing. + +- **credentials** (optional) *map[string]CredentialRequest* + + Here the executor may request the provisioning of some credentials with a + dedicated name/purpose and structure. If specified it will be propagated + to a using package. It this uses an own credentials section, this one + will be filtered and checked for the actual executor. + +- **outputs** (optional) *map[string]OutputSpecification* + + This field can be used to describe the provided outputs of this executor. + The *OutputSpecification* contains only the field description, + so far. It is intended to be extended to contain further information to more + formally describe the type of output. + +- **image** (development) *object* + + Instead of an imageRef it is possible to directly specify an + absolute image. + + ATTENTION: this is intended for development purposes, ONLY. Do not use it + for final component versions. + + It has the field ref and the optional field digest. + +### Client Parameters + +Common to all executors a parameter file can be provided by the caller. The +package specification may provide a [spiff template](https://github.com/mandelsoft/spiff) +for this parameter file. It can be used, for example to provide useful defaults. +The actually provided content is merged with this template. + +To validate user configuration a JSON scheme can be provided. The user input is +validated first against this scheme before the actual merge is done. + +### Credentials + +Additionally credentials can be requested to be provided by a client. +This is done with the credentials field. It is a map +of credentials names and their meaning and/or handling. + +It uses the following fields: + +- **description** *string* + + This field should describe the purpose of the credential. + +- **properties** *map[string]string* + + This field should describe the used credential fields + +- **consumerId** *map[string]* + + This field can be used to optionally define a consumer id that should be set + in the OCM support library, if used by the executor. At least the field + type and one additional field must be set. + +Credentials are provided in an ocm config file (see [ocm configfile](ocm_configfile.md)). +It uses a memory credential repository with the name default +to store the credentials under the given name. Additionally appropriate +consumer ids will be propagated, if requested in the credentials request config. + +### Executor Image Contract + +The executor image is called with the action as additional argument. It is +expected that is defines a default entry point and a potentially empty list of +standard arguments. + +It is called with two arguments: +- name of the action to execute +- identity of the component version containing the package the executor + is executed for. + + This can be used to access the component descriptor to get access to + further described resources in the executor config + +The container used to execute the executor image gets prepared a standard +filesystem structure used to provide all the executor inputs before the +execution and reading provided executor outputs after the execution. + +
+/
+└── toi
+    ├── inputs
+    │   ├── config      configuration from package specification
+    │   ├── ocmrepo     OCM filesystem repository containing the complete
+    │   │               component version of the package
+    │   └── parameters  merged complete parameter file
+    ├── outputs
+    │   ├── <out>       any number of arbitrary output data provided
+    │   │               by executor
+    │   └── ...         
+    └── run             good practice: typical location for the executed command
+
+ +After processing it is possible to return named outputs. The name of an output +must be a filename. The executor section in the package specification maps those +files to logical outputs in the outputs section. + +
+ <file name by executor> -> <logical output name> +
+ +Basically the output may contain any data, but is strongly recommended +to use yaml or json files, only. This enables further formal processing +by the TOI toolset. + + +### Examples + +``` +description: | + This package is just an example. +executors: + - actions: + - install + resourceRef: + resource: + name: installerimage + config: + level: info +# parameterMapping: # optional spiff mapping of Package configuration to +# .... # executor parameters + outputs: + test: bla +credentials: + target: + description: kubeconfig for target kubernetes cluster + consumerId: + type: Kubernetes + purpose: target +configTemplate: + parameters: + username: admin + password: (( &merge )) +configScheme: + type: object + required: + - parameters + additionalProperties: false + properties: + parameters: + type: object + required: + - password + additionalProperties: false + properties: + username: + type: string + password: + type: string +additionalResources: + configFile: + resource: + name: config-file +``` + +### SEE ALSO + +##### Parents + +* [ocm describe package](ocm_describe_package.md) — describe TOI package +* [ocm describe](ocm_describe.md) — Describe various elements by using appropriate sub commands. +* [ocm](ocm.md) — Open Component Model command line client + + + +##### Additional Links + +* [ocm bootstrap componentversions](ocm_bootstrap_componentversions.md) +* [ocm bootstrap config](ocm_bootstrap_config.md) +* [ocm configfile](ocm_configfile.md) — configuration file + diff --git a/docs/reference/ocm_logging.md b/docs/reference/ocm_logging.md new file mode 100644 index 000000000..41341cbe0 --- /dev/null +++ b/docs/reference/ocm_logging.md @@ -0,0 +1,54 @@ +## ocm logging — Configured Logging Keys + +### Description + + +Logging can be configured as part of the ocm config file ([ocm configfile](ocm_configfile.md)) +or by command line options of the [ocm](ocm.md) command. Details about +the YAML structure of a logging settings can be found on https://github.com/mandelsoft/logging. + +The command line also supports some quick-config options for enabling log levels +for dedicated tags and realms (logging keys). + +The following *tags* are used by the command line tool: + + - blobhandler: execution of blob handler used to upload resource blobs to an ocm repository. + + + +The following *realms* are used by the command line tool: + + - ocm: general realm used for the ocm go library. + - ocm/accessmethod/ociartifact: access method ociArtifact + - ocm/credentials/dockerconfig: docker config handling as credential repository + - ocm/oci.ocireg: OCI repository handling + - ocm/ocm/toi: TOI logging + - ocm/plugins: OCM plugin handling + - ocm/processing: output processing chains + - ocm/transfer: OCM transfer handling + + + +### Examples + +``` +type: logging.config.ocm.software + contextType: attributes.context.ocm.software + settings: + defaultLevel: Info + rules: + - ... +``` + +### SEE ALSO + +##### Parents + +* [ocm](ocm.md) — Open Component Model command line client + + + +##### Additional Links + +* [ocm configfile](ocm_configfile.md) — configuration file + diff --git a/docs/reference/ocm_toi-bootstrapping.md b/docs/reference/ocm_toi-bootstrapping.md index 1ad69d95a..73bec311c 100644 --- a/docs/reference/ocm_toi-bootstrapping.md +++ b/docs/reference/ocm_toi-bootstrapping.md @@ -53,11 +53,15 @@ information required to control the instantiation of an executor. It has the following format: +- **description** (optional) *string* + + A short description of the installation package and some configuration hints. + - **executors** *[]ExecutorSpecification* - **configTemplate** (optional) *yaml* - This a [spiff](https://github.com/mandelsoft/spiff) template used to generate + This is a [spiff](https://github.com/mandelsoft/spiff) template used to generate The user config that is finally passed to the executor. If no template is specified the user parameter input will be processed directly without template. @@ -78,6 +82,17 @@ It has the following format: requites the specification of a credentials file providing the information how to satisfy those credential requests. +- **additionalResources** (optional) *map[string]ResourceReference* + + A set of additional resources specified by OCM resource references. + The key describes the meaning of the resource. The following keys have + a special meaning: + + - **configFile**: an example template for a parameter file + - **credentialsFile**: an example template for a credentials file + + Those templates can be downloaded with [ocm bootstrap config](ocm_bootstrap_config.md). + #### *ExecutorSpecification* The executor specification describes the available actions and their mapping @@ -304,6 +319,8 @@ by the TOI toolset. ### Examples ``` +description: | + This package is just an example. executors: - actions: - install @@ -342,6 +359,10 @@ configScheme: type: string password: type: string +additionalResources: + configFile: + resource: + name: config-file ``` ### SEE ALSO @@ -354,6 +375,7 @@ configScheme: ##### Additional Links -* [ocm bootstrap componentversions](ocm_bootstrap_componentversions.md) — bootstrap component version +* [ocm bootstrap componentversions](ocm_bootstrap_componentversions.md) +* [ocm bootstrap config](ocm_bootstrap_config.md) * [ocm configfile](ocm_configfile.md) — configuration file diff --git a/docs/reference/ocm_transfer_commontransportarchive.md b/docs/reference/ocm_transfer_commontransportarchive.md index 91a465c7b..9d3e840cb 100644 --- a/docs/reference/ocm_transfer_commontransportarchive.md +++ b/docs/reference/ocm_transfer_commontransportarchive.md @@ -11,9 +11,12 @@ ocm transfer commontransportarchive [] ``` -V, --copy-resources transfer referenced resources by-value -h, --help help for commontransportarchive + --lookup stringArray repository name or spec for closure lookup fallback -f, --overwrite overwrite existing component versions + -r, --recursive follow component reference nesting --script string config name of transfer handler script -s, --scriptFile string filename of transfer handler script + -E, --stop-on-existing stop on existing component version in target repository -t, --type string archive format (directory, tar, tgz) (default "directory") --uploader = repository uploader (:[:]=] Transfer content of a Common Transport Archive to the given target repository. +With the option --recursive the complete reference tree of a component reference is traversed. + +If a component lookup for building a reference closure is required +the --lookup option can be used to specify a fallback +lookup repository. +By default the component versions are searched in the repository +holding the component version for which the closure is determined. +For *Component Archives* this is never possible, because it only +contains a single component version. Therefore, in this scenario +this option must always be specified to be able to follow component +references. + The --type option accepts a file format for the target archive to use. The following formats are supported: - directory @@ -40,6 +55,11 @@ resources in the target repository. This behaviour can be further influenced by specifying a transfer script with the script option family. +It the option --stop-on-existing is given together with the --recursive +option, the recursion is stopped for component versions already existing in the +target repository. This behaviour can be further influenced by specifying a transfer script +with the script option family. + If the --uploader option is specified, appropriate uploaders are configured for the transport target. It has the following format diff --git a/docs/reference/ocm_transfer_componentversions.md b/docs/reference/ocm_transfer_componentversions.md index 79797b2e6..005330c55 100644 --- a/docs/reference/ocm_transfer_componentversions.md +++ b/docs/reference/ocm_transfer_componentversions.md @@ -19,6 +19,7 @@ ocm transfer componentversions [] {} --repo string repository name or spec --script string config name of transfer handler script -s, --scriptFile string filename of transfer handler script + -E, --stop-on-existing stop on existing component version in target repository -t, --type string archive format (directory, tar, tgz) (default "directory") --uploader = repository uploader (:[:]=script option family. +It the option --stop-on-existing is given together with the --recursive +option, the recursion is stopped for component versions already existing in the +target repository. This behaviour can be further influenced by specifying a transfer script +with the script option family. + If the --uploader option is specified, appropriate uploaders are configured for the transport target. It has the following format diff --git a/examples/lib/config.md b/examples/lib/config.md index 351159bb6..7780c22ab 100644 --- a/examples/lib/config.md +++ b/examples/lib/config.md @@ -73,7 +73,7 @@ context, which is used to apply the configuration object. After the object has been applied, the result can be observed on the intended target object. For a credential configuration this is the credential context. Here, the credentials for the configured consumer id can -be queried. In the example this is a crednetials object valid for an +be queried. In the example this is a credentials object valid for an OCI registry. ```go diff --git a/go.mod b/go.mod index da817318a..7d199d1a9 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/ghodss/yaml v1.0.0 github.com/go-logr/logr v1.2.3 github.com/mandelsoft/filepath v0.0.0-20220503095057-4432a2285b68 - github.com/mandelsoft/spiff v1.7.0-beta-3 + github.com/mandelsoft/spiff v1.3.0-beta-7.0.20230328214234-980037cf1bd7 github.com/modern-go/reflect2 v1.0.2 github.com/onsi/gomega v1.26.0 github.com/opencontainers/go-digest v1.0.0 @@ -45,7 +45,7 @@ require ( github.com/google/go-github/v45 v45.2.0 github.com/klauspost/compress v1.15.11 github.com/klauspost/pgzip v1.2.5 - github.com/mandelsoft/logging v0.0.0-20221114215048-ab754b164dd6 + github.com/mandelsoft/logging v0.0.0-20230331123830-36542ef18f6f github.com/mandelsoft/vfs v0.0.0-20220805210647-bf14a11bfe31 github.com/marstr/guid v1.1.0 github.com/mitchellh/copystructure v1.2.0 @@ -110,6 +110,7 @@ require ( github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/fatih/color v1.13.0 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fvbommel/sortorder v1.0.2 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect @@ -131,6 +132,7 @@ require ( github.com/gorilla/mux v1.8.0 // indirect github.com/gosuri/uitable v0.0.4 // indirect github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect github.com/huandu/xstrings v1.3.3 // indirect github.com/imdario/mergo v0.3.13 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect @@ -142,6 +144,7 @@ require ( github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/lib/pq v1.10.7 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect + github.com/magiconair/properties v1.8.6 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.16 // indirect @@ -149,6 +152,7 @@ require ( github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/miekg/pkcs11 v1.1.1 // indirect github.com/mitchellh/go-wordwrap v1.0.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/locker v1.0.1 // indirect github.com/moby/spdystream v0.2.0 // indirect @@ -174,7 +178,9 @@ require ( github.com/shopspring/decimal v1.2.0 // indirect github.com/spf13/afero v1.9.2 // indirect github.com/spf13/cast v1.5.0 // indirect - github.com/spf13/viper v1.13.0 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/viper v1.14.0 // indirect + github.com/subosito/gotenv v1.4.1 // indirect github.com/theupdateframework/notary v0.7.0 // indirect github.com/vbatts/tar-split v0.11.2 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect @@ -189,12 +195,13 @@ require ( golang.org/x/term v0.5.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.4.0 // indirect - golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006 // indirect - google.golang.org/grpc v1.49.0 // indirect + google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e // indirect + google.golang.org/grpc v1.50.1 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/apiserver v0.26.1 // indirect k8s.io/client-go v0.26.1 // indirect diff --git a/go.sum b/go.sum index 25a0a3f59..239f74062 100644 --- a/go.sum +++ b/go.sum @@ -501,6 +501,7 @@ github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= github.com/fvbommel/sortorder v1.0.2 h1:mV4o8B2hKboCdkJm+a7uX/SIpZob4JzUpc5GGnM45eo= github.com/fvbommel/sortorder v1.0.2/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= @@ -855,6 +856,7 @@ github.com/magiconair/properties v1.5.3/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.3/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= +github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -869,6 +871,28 @@ github.com/mandelsoft/filepath v0.0.0-20220503095057-4432a2285b68 h1:99GWPlKVS11 github.com/mandelsoft/filepath v0.0.0-20220503095057-4432a2285b68/go.mod h1:n4xEiUD2HNHnn2w5ZKF0qgjDecHVCWAl5DxZ7+pcFU8= github.com/mandelsoft/logging v0.0.0-20221114215048-ab754b164dd6 h1:xlUJPmMJo1IVQVn42ELLDUNY3sfwzKVp4kyuy7Q2QwI= github.com/mandelsoft/logging v0.0.0-20221114215048-ab754b164dd6/go.mod h1:vxGpzOesdqLGRSWb4fz5DaH0ekJvkISst4qq6UZ2xFA= +github.com/mandelsoft/logging v0.0.0-20230325172347-f3cf57b57a3b h1:tXUSiD3moa3MNo2LYcExdtGFeIS9q/c2GfrIYAp7F4A= +github.com/mandelsoft/logging v0.0.0-20230325172347-f3cf57b57a3b/go.mod h1:vxGpzOesdqLGRSWb4fz5DaH0ekJvkISst4qq6UZ2xFA= +github.com/mandelsoft/logging v0.0.0-20230325174853-f20defb47faf h1:7Gvyx+6u7HjY43AB4nfoFVooHHn0fQTd6/2amJO7zeE= +github.com/mandelsoft/logging v0.0.0-20230325174853-f20defb47faf/go.mod h1:vxGpzOesdqLGRSWb4fz5DaH0ekJvkISst4qq6UZ2xFA= +github.com/mandelsoft/logging v0.0.0-20230325214959-131c060883d4 h1:nLdjq79y8T8CLiq8YEYNMA/2HxlwgcraU2zrMHlK2Yc= +github.com/mandelsoft/logging v0.0.0-20230325214959-131c060883d4/go.mod h1:vxGpzOesdqLGRSWb4fz5DaH0ekJvkISst4qq6UZ2xFA= +github.com/mandelsoft/logging v0.0.0-20230326142042-d490c9985c67 h1:SC3plxm4LxgO6chfw+MgkiLqt0G+rla0CbKo5rBCoKk= +github.com/mandelsoft/logging v0.0.0-20230326142042-d490c9985c67/go.mod h1:HjtBPH5tsJygvyYEu2r/ZwOOBGcu+oR2sKH73VEnHtg= +github.com/mandelsoft/logging v0.0.0-20230327224233-24ba6742855a h1:QqNkiyg3nNJPlE5K/wYFNzRJSGWyhRNlO3vYUmGmXps= +github.com/mandelsoft/logging v0.0.0-20230327224233-24ba6742855a/go.mod h1:HjtBPH5tsJygvyYEu2r/ZwOOBGcu+oR2sKH73VEnHtg= +github.com/mandelsoft/logging v0.0.0-20230328095932-6b1146a14c17 h1:uOqccIJP7pm5+Wo7x9APJhVqUH6M31pARjAz5enxHJ4= +github.com/mandelsoft/logging v0.0.0-20230328095932-6b1146a14c17/go.mod h1:HjtBPH5tsJygvyYEu2r/ZwOOBGcu+oR2sKH73VEnHtg= +github.com/mandelsoft/logging v0.0.0-20230328100121-5dc635fd1fa2 h1:Spc+g9G9nVXf83AWCLSfA4hzlhtI8LF4mvEMHoDErgk= +github.com/mandelsoft/logging v0.0.0-20230328100121-5dc635fd1fa2/go.mod h1:HjtBPH5tsJygvyYEu2r/ZwOOBGcu+oR2sKH73VEnHtg= +github.com/mandelsoft/logging v0.0.0-20230328101239-150fce59b495 h1:WmsLafaYjuxZs+KK5ANPE0oT1b+taoZ6q0nJty5WxJo= +github.com/mandelsoft/logging v0.0.0-20230328101239-150fce59b495/go.mod h1:HjtBPH5tsJygvyYEu2r/ZwOOBGcu+oR2sKH73VEnHtg= +github.com/mandelsoft/logging v0.0.0-20230328114449-3d7d8773f059 h1:0wlHM3kOWz/WzbGCqHR8golLW+HPf364+iFhEXC/hn4= +github.com/mandelsoft/logging v0.0.0-20230328114449-3d7d8773f059/go.mod h1:HjtBPH5tsJygvyYEu2r/ZwOOBGcu+oR2sKH73VEnHtg= +github.com/mandelsoft/logging v0.0.0-20230331123830-36542ef18f6f h1:ZoqAURpfhE54QFLpcRot25aZEA0dfLVcGh0DmG5qLm4= +github.com/mandelsoft/logging v0.0.0-20230331123830-36542ef18f6f/go.mod h1:HjtBPH5tsJygvyYEu2r/ZwOOBGcu+oR2sKH73VEnHtg= +github.com/mandelsoft/spiff v1.3.0-beta-7.0.20230328214234-980037cf1bd7 h1:0dHDK+SxCJHTCLIDuafGyeoo50BPYlfjQqihgUA8e6E= +github.com/mandelsoft/spiff v1.3.0-beta-7.0.20230328214234-980037cf1bd7/go.mod h1:TwEeOPuRZxlzQBCLEyVTlHmBSruSGGNdiQ2fovVJ8ao= github.com/mandelsoft/spiff v1.7.0-beta-3 h1:AvZldpnctpyfQqtAA5uxokD5rlCK52mGAXxg7tnW5Ag= github.com/mandelsoft/spiff v1.7.0-beta-3/go.mod h1:3Kg6qrggWO4oc1k++acd6xhcWisJ2YkzxpVZpuYONGg= github.com/mandelsoft/vfs v0.0.0-20201002080026-d03d33d5889a/go.mod h1:74aV7kulg9C434HiI3zNALN79QHc9IZMN+SI4UdLn14= @@ -934,6 +958,7 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= @@ -1195,6 +1220,8 @@ github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c/go.mod h1:A8kyI5cUJhb8 github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU= github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= +github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU= +github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -1217,6 +1244,7 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= +github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/sylabs/sif/v2 v2.7.0/go.mod h1:TiyBWsgWeh5yBeQFNuQnvROwswqK7YJT8JA1L53bsXQ= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= @@ -1607,6 +1635,7 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1717,6 +1746,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= @@ -1804,6 +1835,8 @@ google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006 h1:mmbq5q8M1t7dhkLw320YK4PsOXm6jdnUAkErImaIqOg= google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw= +google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e h1:S9GbmC1iCgvbLyAokVCwiO6tVIrU9Y7c5oMx1V/ki/Y= +google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -1836,6 +1869,8 @@ google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ5 google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1871,6 +1906,7 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.61.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/pipe.v2 v2.0.0-20140414041502-3c2ca4d52544/go.mod h1:UhTeH/yXCK/KY7TX24mqPkaQ7gZeqmWd/8SSS8B3aHw= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= diff --git a/hack/githubcreds.sh b/hack/githubcreds.sh new file mode 100755 index 000000000..0fd96ab22 --- /dev/null +++ b/hack/githubcreds.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +# +# SPDX-License-Identifier: Apache-2.0 + +createAuth() { + if [ -n "$GITHUB_REPOSITORY_OWNER" -a -n "$GITHUB_TOKEN" ]; then + ocm_comprepo="ghcr.io/$GITHUB_REPOSITORY_OWNER/ocm" + ocm_comprepouser=$GITHUB_REPOSITORY_OWNER + ocm_comprepopassword="$GITHUB_TOKEN" + comprepourl="${ocm_comprepo#*//}" + repohost="${comprepourl%%/*}" + comprepourl="${ocm_comprepo%$comprepourl}${comprepourl%%/*}" + creds=(--cred :type=OCIRegistry --cred ":hostname=$repohost" --cred "username=$ocm_comprepouser" --cred "password=$ocm_comprepopassword") + + echo "${creds[@]}" + fi +} + +createAuth diff --git a/pkg/cobrautils/logopts/options.go b/pkg/cobrautils/logopts/options.go new file mode 100644 index 000000000..139d07bda --- /dev/null +++ b/pkg/cobrautils/logopts/options.go @@ -0,0 +1,137 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package logopts + +import ( + "strings" + + "github.com/mandelsoft/logging" + "github.com/mandelsoft/logging/config" + "github.com/mandelsoft/logging/logrusr" + "github.com/mandelsoft/vfs/pkg/vfs" + "github.com/sirupsen/logrus" + "github.com/spf13/pflag" + + "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/errors" +) + +var Description = ` +The --log* options can be used to configure the logging behaviour. +There is a quick config option --log-keys 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, which matches all logging realms +with the given realms as path prefix. 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 (=) +optiobally a dedicated level can be specified. Log levels can be (error, +warn, info, debug and trace. +The default level is warn. +` + +type Options struct { + LogLevel string + LogFileName string + LogConfig string + LogKeys []string + + LogFile vfs.File + LogForward *config.Config +} + +func (o *Options) AddFlags(fs *pflag.FlagSet) { + fs.StringVarP(&o.LogLevel, "loglevel", "l", "", "set log level") + fs.StringVarP(&o.LogFileName, "logfile", "L", "", "set log file") + fs.StringVarP(&o.LogConfig, "logconfig", "", "", "log config") + fs.StringArrayVarP(&o.LogKeys, "logkeys", "", nil, "log tags/realms(.) to be enabled ([.]name{,[.]name}[=level])") +} + +func (o *Options) Close() error { + if o.LogFile == nil { + return nil + } + return o.LogFile.Close() +} + +func (o *Options) Configure(ctx ocm.Context, logctx logging.Context) error { + var err error + + if logctx == nil { + // by default: always configure the root logging context used for the actual ocm context. + logctx = ctx.LoggingContext() + for logctx.Tree().GetBaseContext() != nil { + logctx = logctx.Tree().GetBaseContext() + } + } + + if o.LogLevel != "" { + l, err := logging.ParseLevel(o.LogLevel) + if err != nil { + return errors.Wrapf(err, "invalid log level %q", o.LogLevel) + } + logctx.SetDefaultLevel(l) + } else { + logctx.SetDefaultLevel(logging.ErrorLevel) + } + logcfg := &config.Config{DefaultLevel: logging.LevelName(logctx.GetDefaultLevel())} + + fs := vfsattr.Get(ctx) + if o.LogFileName != "" { + o.LogFile, err = fs.OpenFile(o.LogFileName, vfs.O_CREATE|vfs.O_WRONLY, 0o600) + if err != nil { + return errors.Wrapf(err, "cannot open log file %q", o.LogFile) + } + log := logrus.New() + log.SetFormatter(&logrus.JSONFormatter{TimestampFormat: "2006-01-02 15:04:05"}) + log.SetOutput(o.LogFile) + logctx.SetBaseLogger(logrusr.New(log)) + } + + if o.LogConfig != "" { + cfg, err := vfs.ReadFile(fs, o.LogConfig) + if err != nil { + return errors.Wrapf(err, "cannot read logging config %q", o.LogFile) + } + if err = config.ConfigureWithData(logctx, cfg); err != nil { + return errors.Wrapf(err, "invalid logging config: %q", o.LogFile) + } + } + + for _, t := range o.LogKeys { + level := logging.InfoLevel + i := strings.Index(t, "=") + if i >= 0 { + level, err = logging.ParseLevel(t[i+1:]) + if err != nil { + return errors.Wrapf(err, "invalid log tag setting") + } + t = t[:i] + } + var cond []logging.Condition + var cfgcond []config.Condition + + for _, tag := range strings.Split(t, ",") { + tag = strings.TrimSpace(tag) + if strings.HasPrefix(tag, "/") { + cond = append(cond, logging.NewRealmPrefix(tag[1:])) + cfgcond = append(cfgcond, config.RealmPrefix(tag[1:])) + } else { + cond = append(cond, logging.NewTag(tag)) + cfgcond = append(cfgcond, config.Tag(tag)) + } + } + rule := logging.NewConditionRule(level, cond...) + logcfg.Rules = append(logcfg.Rules, config.ConditionalRule(logging.LevelName(level), cfgcond...)) + logctx.AddRule(rule) + } + o.LogForward = logcfg + logforward.Set(ctx.AttributesContext(), logcfg) + + return nil +} diff --git a/pkg/cobrautils/logopts/options_test.go b/pkg/cobrautils/logopts/options_test.go new file mode 100644 index 000000000..186f102f5 --- /dev/null +++ b/pkg/cobrautils/logopts/options_test.go @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package logopts + +import ( + "fmt" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/open-component-model/ocm/pkg/testutils" + + "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{ + LogLevel: "debug", + LogKeys: []string{ + "tag=trace", + "/realm=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("realm/test")).Enabled(logging.InfoLevel)).To(BeTrue()) + Expect(logctx.Logger(logging.NewRealm("realm/test")).Enabled(logging.DebugLevel)).To(BeFalse()) + }) +}) diff --git a/pkg/cobrautils/logopts/suite_test.go b/pkg/cobrautils/logopts/suite_test.go new file mode 100644 index 000000000..9b4c99c00 --- /dev/null +++ b/pkg/cobrautils/logopts/suite_test.go @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package logopts + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestConfig(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Logging Options") +} diff --git a/pkg/contexts/credentials/config/type.go b/pkg/contexts/credentials/config/type.go index 92e1329a1..da71c6153 100644 --- a/pkg/contexts/credentials/config/type.go +++ b/pkg/contexts/credentials/config/type.go @@ -27,7 +27,7 @@ func init() { 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 crednetials + // 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. diff --git a/pkg/contexts/credentials/interface.go b/pkg/contexts/credentials/interface.go index b58209494..ef2bb26b1 100644 --- a/pkg/contexts/credentials/interface.go +++ b/pkg/contexts/credentials/interface.go @@ -9,6 +9,7 @@ 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/oci/identity" "github.com/open-component-model/ocm/pkg/runtime" ) @@ -106,3 +107,15 @@ var ( NoMatch = internal.NoMatch PartialMatch = internal.PartialMatch ) + +func NewConsumerIdentity(typ string, attrs ...string) ConsumerIdentity { + r := map[string]string{} + r[identity.ID_TYPE] = typ + + i := 0 + for len(attrs) > i { + r[attrs[i]] = r[attrs[i+1]] + i += 2 + } + return r +} diff --git a/pkg/contexts/credentials/repositories/dockerconfig/logging.go b/pkg/contexts/credentials/repositories/dockerconfig/logging.go new file mode 100644 index 000000000..4f24fc88e --- /dev/null +++ b/pkg/contexts/credentials/repositories/dockerconfig/logging.go @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.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/repository.go b/pkg/contexts/credentials/repositories/dockerconfig/repository.go index 15bddcfb7..d36bd12cb 100644 --- a/pkg/contexts/credentials/repositories/dockerconfig/repository.go +++ b/pkg/contexts/credentials/repositories/dockerconfig/repository.go @@ -94,7 +94,7 @@ func (r *Repository) Read(force bool) error { if err != nil { return fmt.Errorf("failed to load config: %w", err) } - log := r.ctx.Logger() + log := r.ctx.Logger(REALM) defaultStore := dockercred.DetectDefaultStore(cfg.CredentialsStore) store := dockercred.NewNativeStore(cfg, defaultStore) // get default native credential store diff --git a/pkg/contexts/datacontext/attrs/init.go b/pkg/contexts/datacontext/attrs/init.go index 45f3b7012..4c9dc6bf8 100644 --- a/pkg/contexts/datacontext/attrs/init.go +++ b/pkg/contexts/datacontext/attrs/init.go @@ -5,6 +5,7 @@ package attrs import ( + _ "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/logforward" _ "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 new file mode 100644 index 000000000..1732bd3c6 --- /dev/null +++ b/pkg/contexts/datacontext/attrs/logforward/attr.go @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.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{}) +} + +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 tool. +(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/config/logging/type.go b/pkg/contexts/datacontext/config/logging/type.go index e7f8a69e2..984024053 100644 --- a/pkg/contexts/datacontext/config/logging/type.go +++ b/pkg/contexts/datacontext/config/logging/type.go @@ -5,14 +5,11 @@ package logging import ( - "encoding/json" - "github.com/mandelsoft/logging" logcfg "github.com/mandelsoft/logging/config" "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/errors" local "github.com/open-component-model/ocm/pkg/logging" "github.com/open-component-model/ocm/pkg/runtime" ) @@ -37,39 +34,35 @@ type Config struct { ContextType string `json:"contextType,omitempty"` Settings logcfg.Config `json:"settings"` - // ExtraId is used to the context type "default" to be able + // ExtraId is used to the context type "default" 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"` } -// NewConfigSpec creates a new memory ConfigSpec. +// New creates a logging config specification. func New(ctxtype string, deflvl int) *Config { return &Config{ ObjectVersionedType: runtime.NewVersionedObjectType(ConfigType), ContextType: ctxtype, Settings: logcfg.Config{ DefaultLevel: logging.LevelName(deflvl), - Rules: []json.RawMessage{}, }, } } -func (c *Config) AddRuleSpec(spec interface{}) error { - var err error - - data, ok := spec.([]byte) - if !ok { - data, err = json.Marshal(spec) - if err != nil { - errors.Wrapf(err, "invalid logging rule specification") - } - } - _, err = logcfg.DefaultRegistry().CreateRule(data) - if err != nil { - return errors.Wrapf(err, "invalid logging rule specification") +// NewWithConfig creates a logging config specification from a +// logging config object. +func NewWithConfig(ctxtype string, cfg *logcfg.Config) *Config { + return &Config{ + ObjectVersionedType: runtime.NewVersionedObjectType(ConfigType), + ContextType: ctxtype, + Settings: *cfg, } - c.Settings.Rules = append(c.Settings.Rules, data) +} + +func (c *Config) AddRuleSpec(r logcfg.Rule) error { + c.Settings.Rules = append(c.Settings.Rules, r) return nil } @@ -90,7 +83,10 @@ func (c *Config) ApplyTo(ctx cpi.Context, target interface{}) error { case "default": return local.Configure(&c.Settings, c.ExtraId) - // configure loogging context providers. + case "global": + return local.ConfigureGlobal(&c.Settings, c.ExtraId) + + // configure logging context providers. case "": if _, ok := target.(datacontext.AttributesContext); !ok { return cpi.ErrNoContext("attribute context") diff --git a/pkg/contexts/datacontext/context.go b/pkg/contexts/datacontext/context.go index 7070a790b..18b06fa7f 100644 --- a/pkg/contexts/datacontext/context.go +++ b/pkg/contexts/datacontext/context.go @@ -173,6 +173,7 @@ type delegates = Delegates type contextBase struct { ctxtype string + id int64 key interface{} effective Context attributes Attributes @@ -227,6 +228,7 @@ func NewWithActions(parentAttrs Attributes, actions handlers.Registry) Attribute c.Context = &contextBase{ ctxtype: CONTEXT_TYPE, + id: contextrange.NextId(), key: key, effective: c, attributes: newAttributes(c, parentAttrs, &c.updater), @@ -271,8 +273,26 @@ func (c *_context) Logger(messageContext ...logging.MessageContext) logging.Logg //////////////////////////////////////////////////////////////////////////////// +// NumberRange can be used as source for successive id numbers to tag +// elements, since debuggers not always sow object addresses. +type NumberRange struct { + id int64 + lock sync.Mutex +} + +func (n *NumberRange) NextId() int64 { + n.lock.Lock() + defer n.lock.Unlock() + + n.id++ + return n.id +} + +var contextrange, attrsrange = NumberRange{}, NumberRange{} + type _attributes struct { sync.RWMutex + id int64 ctx Context parent Attributes updater *Updater @@ -287,6 +307,7 @@ func NewAttributes(ctx Context, parent Attributes, updater *Updater) Attributes func newAttributes(ctx Context, parent Attributes, updater *Updater) *_attributes { return &_attributes{ + id: attrsrange.NextId(), ctx: ctx, parent: parent, updater: updater, diff --git a/pkg/contexts/oci/repositories/artifactset/utils_synthesis.go b/pkg/contexts/oci/repositories/artifactset/utils_synthesis.go index 6d86484dc..ce92f9ef6 100644 --- a/pkg/contexts/oci/repositories/artifactset/utils_synthesis.go +++ b/pkg/contexts/oci/repositories/artifactset/utils_synthesis.go @@ -7,6 +7,8 @@ package artifactset import ( "fmt" + . "github.com/open-component-model/ocm/pkg/finalizer" + "github.com/mandelsoft/vfs/pkg/osfs" "github.com/mandelsoft/vfs/pkg/vfs" "github.com/opencontainers/go-digest" @@ -17,7 +19,6 @@ import ( "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/errors" - "github.com/open-component-model/ocm/pkg/utils" ) const SynthesizedBlobFormat = "+tar+gzip" @@ -105,9 +106,9 @@ type ArtifactIterator func() (ArtifactFactory, bool, error) type ArtifactFeedback func(blob accessio.BlobAccess, art cpi.ArtifactAccess) error // ArtifactTransferCreator provides an ArtifactFactory transferring the given artifact. -func ArtifactTransferCreator(art cpi.ArtifactAccess, finalizer *utils.Finalizer, feedback ...ArtifactFeedback) ArtifactFactory { +func ArtifactTransferCreator(art cpi.ArtifactAccess, finalizer *Finalizer, feedback ...ArtifactFeedback) ArtifactFactory { return func(set *ArtifactSet) (digest.Digest, string, error) { - var f utils.Finalizer + var f Finalizer defer f.Finalize() f.Include(finalizer) diff --git a/pkg/contexts/oci/repositories/ocireg/logging.go b/pkg/contexts/oci/repositories/ocireg/logging.go index 87f99e62a..70eae4512 100644 --- a/pkg/contexts/oci/repositories/ocireg/logging.go +++ b/pkg/contexts/oci/repositories/ocireg/logging.go @@ -5,7 +5,7 @@ package ocireg import ( - "github.com/mandelsoft/logging" + ocmlog "github.com/open-component-model/ocm/pkg/logging" ) -var REALM = logging.NewRealm("ocireg") +var REALM = ocmlog.DefineSubRealm("OCI repository handling", "oci.ocireg") diff --git a/pkg/contexts/oci/repositories/ocireg/repository.go b/pkg/contexts/oci/repositories/ocireg/repository.go index 9d3de8203..6491fcd7a 100644 --- a/pkg/contexts/oci/repositories/ocireg/repository.go +++ b/pkg/contexts/oci/repositories/ocireg/repository.go @@ -11,6 +11,7 @@ import ( "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/remotes/docker/config" + "github.com/mandelsoft/logging" "github.com/open-component-model/ocm/pkg/contexts/credentials" "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" @@ -18,7 +19,7 @@ import ( "github.com/open-component-model/ocm/pkg/docker" "github.com/open-component-model/ocm/pkg/docker/resolve" "github.com/open-component-model/ocm/pkg/errors" - "github.com/open-component-model/ocm/pkg/logging" + ocmlog "github.com/open-component-model/ocm/pkg/logging" "github.com/open-component-model/ocm/pkg/utils" ) @@ -43,18 +44,21 @@ func (r *RepositoryInfo) HostInfo() (string, string, string) { } type Repository struct { - ctx cpi.Context - spec *RepositorySpec - info *RepositoryInfo + ctx cpi.Context + logger logging.UnboundLogger + spec *RepositorySpec + info *RepositoryInfo } var _ cpi.Repository = &Repository{} func NewRepository(ctx cpi.Context, spec *RepositorySpec, info *RepositoryInfo) (*Repository, error) { + urs := spec.UniformRepositorySpec() return &Repository{ - ctx: ctx, - spec: spec, - info: info, + ctx: ctx, + logger: logging.DynamicLogger(ctx, REALM, logging.NewAttribute(ocmlog.ATTR_HOST, urs.Host)), + spec: spec, + info: info, }, nil } @@ -84,8 +88,9 @@ func (r *Repository) getResolver(comp string) (resolve.Resolver, error) { return nil, err } } + logger := r.logger.BoundLogger().WithValues(ocmlog.ATTR_NAMESPACE, comp) if creds == nil { - logging.Logger(REALM).Trace("no credentials", "comp", comp, "base", r.spec.BaseURL) + logger.Trace("no credentials") } opts := docker.ResolverOptions{ @@ -100,10 +105,10 @@ func (r *Repository) getResolver(comp string) (resolve.Resolver, error) { if pw != "" { pw = "***" } - logging.Logger(REALM).Trace("query credentials", "host", host, "user", creds.GetProperty(credentials.ATTR_USERNAME), "pass", pw) + logger.Trace("query credentials", ocmlog.ATTR_USER, creds.GetProperty(credentials.ATTR_USERNAME), "pass", pw) return creds.GetProperty(credentials.ATTR_USERNAME), p, nil } - logging.Logger(REALM).Trace("no credentials", "host", host) + logger.Trace("no credentials") return "", "", nil }, DefaultScheme: r.info.Scheme, diff --git a/pkg/contexts/ocm/accessmethods/github/method.go b/pkg/contexts/ocm/accessmethods/github/method.go index 8db9d022d..b56dddc4d 100644 --- a/pkg/contexts/ocm/accessmethods/github/method.go +++ b/pkg/contexts/ocm/accessmethods/github/method.go @@ -115,6 +115,10 @@ func (_ *AccessSpec) IsLocal(cpi.Context) bool { return false } +func (a *AccessSpec) GlobalAccessSpec(ctx cpi.Context) cpi.AccessSpec { + return a +} + func (_ *AccessSpec) GetType() string { return Type } diff --git a/pkg/contexts/ocm/accessmethods/localblob/method.go b/pkg/contexts/ocm/accessmethods/localblob/method.go index 7fee5346f..0c346a588 100644 --- a/pkg/contexts/ocm/accessmethods/localblob/method.go +++ b/pkg/contexts/ocm/accessmethods/localblob/method.go @@ -80,6 +80,10 @@ func (a *AccessSpec) IsLocal(cpi.Context) bool { return true } +func (a *AccessSpec) GlobalAccessSpec(ctx cpi.Context) cpi.AccessSpec { + return a.GlobalAccess +} + func (a *AccessSpec) GetMimeType() string { return a.MediaType } diff --git a/pkg/contexts/ocm/accessmethods/localociblob/method.go b/pkg/contexts/ocm/accessmethods/localociblob/method.go index 8c234fce1..7c1c0c985 100644 --- a/pkg/contexts/ocm/accessmethods/localociblob/method.go +++ b/pkg/contexts/ocm/accessmethods/localociblob/method.go @@ -52,6 +52,10 @@ func (s AccessSpec) IsLocal(context cpi.Context) bool { return true } +func (a *AccessSpec) GlobalAccessSpec(ctx cpi.Context) cpi.AccessSpec { + return nil +} + func (s *AccessSpec) GetMimeType() string { return "" } diff --git a/pkg/contexts/ocm/accessmethods/none/method.go b/pkg/contexts/ocm/accessmethods/none/method.go index f6f075e10..3bdd71116 100644 --- a/pkg/contexts/ocm/accessmethods/none/method.go +++ b/pkg/contexts/ocm/accessmethods/none/method.go @@ -45,10 +45,14 @@ func (a *AccessSpec) Describe(ctx cpi.Context) string { return "none" } -func (s AccessSpec) IsLocal(context cpi.Context) bool { +func (s *AccessSpec) IsLocal(context cpi.Context) bool { return false } +func (s *AccessSpec) GlobalAccessSpec(ctx cpi.Context) cpi.AccessSpec { + return nil +} + func (s *AccessSpec) GetMimeType() string { return "" } diff --git a/pkg/contexts/ocm/accessmethods/npm/method.go b/pkg/contexts/ocm/accessmethods/npm/method.go index d2aeb5bb2..f6f35516c 100644 --- a/pkg/contexts/ocm/accessmethods/npm/method.go +++ b/pkg/contexts/ocm/accessmethods/npm/method.go @@ -73,6 +73,10 @@ func (_ *AccessSpec) IsLocal(cpi.Context) bool { return false } +func (a *AccessSpec) GlobalAccessSpec(ctx cpi.Context) cpi.AccessSpec { + return a +} + func (a *AccessSpec) GetReferenceHint(cv cpi.ComponentVersionAccess) string { return a.Package + ":" + a.Version } diff --git a/pkg/contexts/ocm/accessmethods/ociartifact/logging.go b/pkg/contexts/ocm/accessmethods/ociartifact/logging.go new file mode 100644 index 000000000..68b4b3471 --- /dev/null +++ b/pkg/contexts/ocm/accessmethods/ociartifact/logging.go @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.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...) +} diff --git a/pkg/contexts/ocm/accessmethods/ociartifact/method.go b/pkg/contexts/ocm/accessmethods/ociartifact/method.go index 6b7de1ebd..b9980d776 100644 --- a/pkg/contexts/ocm/accessmethods/ociartifact/method.go +++ b/pkg/contexts/ocm/accessmethods/ociartifact/method.go @@ -10,6 +10,8 @@ import ( "strings" "sync" + . "github.com/open-component-model/ocm/pkg/finalizer" + "github.com/opencontainers/go-digest" "github.com/open-component-model/ocm/pkg/common/accessio" @@ -21,7 +23,6 @@ import ( "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/runtime" - "github.com/open-component-model/ocm/pkg/utils" ) // Type is the access type of a oci registry. @@ -76,6 +77,10 @@ func (_ *AccessSpec) IsLocal(cpi.Context) bool { return false } +func (a *AccessSpec) GlobalAccessSpec(ctx cpi.Context) cpi.AccessSpec { + return a +} + func (a *AccessSpec) GetReferenceHint(cv cpi.ComponentVersionAccess) string { ref, err := oci.ParseRef(a.ImageReference) if err != nil { @@ -111,7 +116,7 @@ type accessMethod struct { comp cpi.ComponentVersionAccess spec *AccessSpec - finalizer utils.Finalizer + finalizer Finalizer err error art oci.ArtifactAccess ref *oci.RefSpec @@ -160,7 +165,7 @@ func (m *accessMethod) eval() (oci.Repository, *oci.RefSpec, error) { return repo, &ref, err } -func (m *accessMethod) GetArtifact(finalizer *utils.Finalizer) (oci.ArtifactAccess, *oci.RefSpec, error) { +func (m *accessMethod) GetArtifact(finalizer *Finalizer) (oci.ArtifactAccess, *oci.RefSpec, error) { repo, ref, err := m.eval() if err != nil { return nil, nil, err @@ -240,9 +245,10 @@ func (m *accessMethod) getBlob() (artifactset.ArtifactBlob, error) { if err != nil { return nil, err } - m.comp.GetContext().Logger().Info("synthesize artifact blob", "ref", m.spec.ImageReference) + logger := Logger(m.comp) + logger.Info("synthesize artifact blob", "ref", m.spec.ImageReference) m.blob, err = artifactset.SynthesizeArtifactBlobForArtifact(art, ref.Version()) - m.comp.GetContext().Logger().Info("synthesize artifact blob done", "ref", m.spec.ImageReference, "error", logging.ErrorMessage(err)) + logger.Info("synthesize artifact blob done", "ref", m.spec.ImageReference, "error", logging.ErrorMessage(err)) if err != nil { return nil, err } diff --git a/pkg/contexts/ocm/accessmethods/ociblob/method.go b/pkg/contexts/ocm/accessmethods/ociblob/method.go index 8565cd76d..fcba686c9 100644 --- a/pkg/contexts/ocm/accessmethods/ociblob/method.go +++ b/pkg/contexts/ocm/accessmethods/ociblob/method.go @@ -64,10 +64,14 @@ func (a *AccessSpec) Describe(ctx cpi.Context) string { return fmt.Sprintf("OCI blob %s in repository %s", a.Digest, a.Reference) } -func (s AccessSpec) IsLocal(context cpi.Context) bool { +func (s *AccessSpec) IsLocal(context cpi.Context) bool { return false } +func (s *AccessSpec) GlobalAccessSpec(ctx cpi.Context) cpi.AccessSpec { + return s +} + func (s *AccessSpec) GetMimeType() string { return s.MediaType } diff --git a/pkg/contexts/ocm/accessmethods/plugin/method.go b/pkg/contexts/ocm/accessmethods/plugin/method.go index 62c9b7672..1a60809ff 100644 --- a/pkg/contexts/ocm/accessmethods/plugin/method.go +++ b/pkg/contexts/ocm/accessmethods/plugin/method.go @@ -41,6 +41,10 @@ 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) } diff --git a/pkg/contexts/ocm/accessmethods/s3/method.go b/pkg/contexts/ocm/accessmethods/s3/method.go index a1e94cc11..30ac324a0 100644 --- a/pkg/contexts/ocm/accessmethods/s3/method.go +++ b/pkg/contexts/ocm/accessmethods/s3/method.go @@ -81,6 +81,10 @@ func (_ *AccessSpec) IsLocal(cpi.Context) bool { return false } +func (a *AccessSpec) GlobalAccessSpec(ctx cpi.Context) cpi.AccessSpec { + return a +} + func (_ *AccessSpec) GetType() string { return Type } diff --git a/pkg/contexts/ocm/blobhandler/oci/ocirepo/blobhandler.go b/pkg/contexts/ocm/blobhandler/oci/ocirepo/blobhandler.go index 65e2ac45d..158fa743f 100644 --- a/pkg/contexts/ocm/blobhandler/oci/ocirepo/blobhandler.go +++ b/pkg/contexts/ocm/blobhandler/oci/ocirepo/blobhandler.go @@ -9,6 +9,8 @@ import ( "path" "strings" + . "github.com/open-component-model/ocm/pkg/finalizer" + "github.com/opencontainers/go-digest" "github.com/open-component-model/ocm/pkg/common/accessio" @@ -27,7 +29,6 @@ import ( storagecontext "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler/oci" "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" "github.com/open-component-model/ocm/pkg/errors" - "github.com/open-component-model/ocm/pkg/utils" ) func init() { @@ -138,7 +139,7 @@ func (b *artifactHandler) StoreBlob(blob cpi.BlobAccess, artType, hint string, g var art oci.ArtifactAccess var err error - var finalizer utils.Finalizer + var finalizer Finalizer defer finalizer.Finalize() keep := keepblobattr.Get(ctx.GetContext()) diff --git a/pkg/contexts/ocm/compdesc/meta/v1/resourceref.go b/pkg/contexts/ocm/compdesc/meta/v1/resourceref.go index 6445e9c4f..f0c049471 100644 --- a/pkg/contexts/ocm/compdesc/meta/v1/resourceref.go +++ b/pkg/contexts/ocm/compdesc/meta/v1/resourceref.go @@ -19,7 +19,7 @@ func NewNestedResourceRef(id Identity, path []Identity) ResourceReference { return ResourceReference{Resource: id, ReferencePath: path} } -func (r *ResourceReference) String() string { +func (r ResourceReference) String() string { s := r.Resource.String() for i := 1; i <= len(r.ReferencePath); i++ { diff --git a/pkg/contexts/ocm/compdesc/versions/v2/validation_test.go b/pkg/contexts/ocm/compdesc/versions/v2/validation_test.go index 772399302..66955e5a6 100644 --- a/pkg/contexts/ocm/compdesc/versions/v2/validation_test.go +++ b/pkg/contexts/ocm/compdesc/versions/v2/validation_test.go @@ -10,7 +10,6 @@ import ( . "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" diff --git a/pkg/contexts/ocm/cpi/interface.go b/pkg/contexts/ocm/cpi/interface.go index 6fa1511ac..ec0f5607e 100644 --- a/pkg/contexts/ocm/cpi/interface.go +++ b/pkg/contexts/ocm/cpi/interface.go @@ -18,7 +18,7 @@ const CONTEXT_TYPE = internal.CONTEXT_TYPE const CommonTransportFormat = internal.CommonTransportFormat -var TAG_BLOBHANDLER = logging.NewTag("blobhandler") +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 { diff --git a/pkg/contexts/ocm/cpi/support/compversaccess.go b/pkg/contexts/ocm/cpi/support/compversaccess.go index 15cbbdf0e..ea7f8c5e6 100644 --- a/pkg/contexts/ocm/cpi/support/compversaccess.go +++ b/pkg/contexts/ocm/cpi/support/compversaccess.go @@ -348,7 +348,7 @@ func (c *componentVersionAccessImpl) SetSource(meta *cpi.SourceMeta, acc compdes // AddResource adds a blob resource to the current archive. func (c *componentVersionAccessImpl) SetResourceBlob(meta *cpi.ResourceMeta, blob cpi.BlobAccess, refName string, global cpi.AccessSpec) error { - c.GetContext().Logger().Info("adding resource blob", "resource", meta.Name) + Logger(c).Info("adding resource blob", "resource", meta.Name) acc, err := c.AddBlob(blob, meta.Type, refName, global) if err != nil { return fmt.Errorf("unable to add blob: %w", err) @@ -362,7 +362,7 @@ func (c *componentVersionAccessImpl) SetResourceBlob(meta *cpi.ResourceMeta, blo } func (c *componentVersionAccessImpl) SetSourceBlob(meta *cpi.SourceMeta, blob cpi.BlobAccess, refName string, global cpi.AccessSpec) error { - c.GetContext().Logger().Info("adding source blob", "source", meta.Name) + Logger(c).Info("adding source blob", "source", meta.Name) acc, err := c.AddBlob(blob, meta.Type, refName, global) if err != nil { return fmt.Errorf("unable to add blob: %w", err) diff --git a/pkg/contexts/ocm/cpi/support/logging.go b/pkg/contexts/ocm/cpi/support/logging.go new file mode 100644 index 000000000..4c79675d0 --- /dev/null +++ b/pkg/contexts/ocm/cpi/support/logging.go @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package support + +import ( + "github.com/mandelsoft/logging" + + "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" +) + +type ContextProvider interface { + GetContext() cpi.Context +} + +func Logger(c ContextProvider, keyValuePairs ...interface{}) logging.Logger { + return c.GetContext().Logger().WithValues(keyValuePairs...) +} diff --git a/pkg/contexts/ocm/internal/accessspecref.go b/pkg/contexts/ocm/internal/accessspecref.go index efc2c620d..76ac96b65 100644 --- a/pkg/contexts/ocm/internal/accessspecref.go +++ b/pkg/contexts/ocm/internal/accessspecref.go @@ -95,6 +95,14 @@ func (a *AccessSpecRef) IsLocal(ctx Context) bool { return false } +func (a *AccessSpecRef) GlobalAccessSpec(ctx Context) AccessSpec { + a.assure(ctx) + if a.evaluated != nil { + return a.evaluated.GlobalAccessSpec(ctx) + } + return nil +} + func (a *AccessSpecRef) AccessMethod(access ComponentVersionAccess) (AccessMethod, error) { if err := a.assure(access.GetContext()); err != nil { return nil, err diff --git a/pkg/contexts/ocm/internal/accesstypes.go b/pkg/contexts/ocm/internal/accesstypes.go index c6ece96c5..18087a067 100644 --- a/pkg/contexts/ocm/internal/accesstypes.go +++ b/pkg/contexts/ocm/internal/accesstypes.go @@ -39,6 +39,7 @@ type AccessSpec interface { compdesc.AccessSpec Describe(Context) string IsLocal(Context) bool + GlobalAccessSpec(Context) AccessSpec AccessMethod(access ComponentVersionAccess) (AccessMethod, error) } @@ -197,6 +198,10 @@ func (_ *UnknownAccessSpec) IsLocal(Context) bool { return false } +func (_ *UnknownAccessSpec) GlobalAccessSpec(Context) AccessSpec { + return nil +} + var _ AccessSpec = &UnknownAccessSpec{} //////////////////////////////////////////////////////////////////////////////// @@ -257,4 +262,15 @@ func (s *GenericAccessSpec) IsLocal(ctx Context) bool { 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) +} + var _ AccessSpec = &GenericAccessSpec{} diff --git a/pkg/contexts/ocm/internal/context.go b/pkg/contexts/ocm/internal/context.go index 19baa5421..67d0c114c 100644 --- a/pkg/contexts/ocm/internal/context.go +++ b/pkg/contexts/ocm/internal/context.go @@ -9,6 +9,8 @@ import ( "reflect" "strings" + . "github.com/open-component-model/ocm/pkg/finalizer" + "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" @@ -17,7 +19,6 @@ import ( "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 @@ -61,10 +62,10 @@ type Context interface { // elements should be added to the finalzer, which can be reopened/created // on-the fly whenever required. Finalize() error - Finalizer() *utils.Finalizer + Finalizer() *Finalizer } -//////////////////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////////////////// var key = reflect.TypeOf(_context{}) @@ -86,7 +87,7 @@ func DefinedForContext(ctx context.Context) (Context, bool) { return nil, ok } -//////////////////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////////////////// type _context struct { datacontext.Context @@ -103,7 +104,7 @@ type _context struct { blobHandlers BlobHandlerRegistry blobDigesters BlobDigesterRegistry aliases map[string]RepositorySpec - finalizer utils.Finalizer + finalizer Finalizer } var _ Context = &_context{} @@ -137,7 +138,7 @@ func (c *_context) Finalize() error { return c.finalizer.Finalize() } -func (c *_context) Finalizer() *utils.Finalizer { +func (c *_context) Finalizer() *Finalizer { return &c.finalizer } diff --git a/pkg/contexts/ocm/plugin/descriptor/const.go b/pkg/contexts/ocm/plugin/descriptor/const.go index 4acdeff95..3ead1874e 100644 --- a/pkg/contexts/ocm/plugin/descriptor/const.go +++ b/pkg/contexts/ocm/plugin/descriptor/const.go @@ -5,10 +5,9 @@ package descriptor import ( - "github.com/mandelsoft/logging" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/action" "github.com/open-component-model/ocm/pkg/errors" + ocmlog "github.com/open-component-model/ocm/pkg/logging" ) const ( @@ -19,4 +18,4 @@ const ( KIND_ACTION = action.KIND_ACTION ) -var TAG = logging.NewTag("plugins") +var REALM = ocmlog.DefineSubRealm("OCM plugin handling", "plugins") diff --git a/pkg/contexts/ocm/plugin/interface.go b/pkg/contexts/ocm/plugin/interface.go index e1f661f94..1253cbdd0 100644 --- a/pkg/contexts/ocm/plugin/interface.go +++ b/pkg/contexts/ocm/plugin/interface.go @@ -16,7 +16,7 @@ const ( KIND_ACTION = descriptor.KIND_ACTION ) -var TAG = descriptor.TAG +var TAG = descriptor.REALM type ( Descriptor = descriptor.Descriptor diff --git a/pkg/contexts/ocm/plugin/plugins/plugins.go b/pkg/contexts/ocm/plugin/plugins/plugins.go index ca72670e4..99c7a1ffc 100644 --- a/pkg/contexts/ocm/plugin/plugins/plugins.go +++ b/pkg/contexts/ocm/plugin/plugins/plugins.go @@ -53,7 +53,7 @@ func (pi *pluginsImpl) GetContext() cpi.Context { func (pi *pluginsImpl) Update() { err := pi.updater.Update() if err != nil { - pi.ctx.Logger(descriptor.TAG).Error("config update failed", "error", err.Error()) + pi.ctx.Logger(descriptor.REALM).Error("config update failed", "error", err.Error()) } } diff --git a/pkg/contexts/ocm/plugin/ppi/interface.go b/pkg/contexts/ocm/plugin/ppi/interface.go index 97f2816ff..43bb7eb9a 100644 --- a/pkg/contexts/ocm/plugin/ppi/interface.go +++ b/pkg/contexts/ocm/plugin/ppi/interface.go @@ -30,7 +30,7 @@ type ( UploadTargetSpecInfo = internal.UploadTargetSpecInfo ) -var TAG = descriptor.TAG +var REALM = descriptor.REALM type Plugin interface { Name() string diff --git a/pkg/contexts/ocm/registration/logging.go b/pkg/contexts/ocm/registration/logging.go new file mode 100644 index 000000000..0ecf52baf --- /dev/null +++ b/pkg/contexts/ocm/registration/logging.go @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package registration + +import ( + "github.com/mandelsoft/logging" + + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/descriptor" +) + +func Logger(c logging.ContextProvider, keyValuePairs ...interface{}) logging.Logger { + return c.LoggingContext().Logger(descriptor.REALM).WithValues(keyValuePairs...) +} diff --git a/pkg/contexts/ocm/registration/registration.go b/pkg/contexts/ocm/registration/registration.go index 420652d0f..2eb1d9d0a 100644 --- a/pkg/contexts/ocm/registration/registration.go +++ b/pkg/contexts/ocm/registration/registration.go @@ -5,8 +5,6 @@ package registration import ( - "github.com/mandelsoft/logging" - "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" @@ -18,13 +16,11 @@ import ( "github.com/open-component-model/ocm/pkg/runtime" ) -var TAG = logging.NewTag("plugins") - -// RegisterExtensions registers all the extension provided by the found plugin -// at the given context. If no context is given, the cache context is used. +// RegisterExtensions registers all the extension provided by the found plugin. func RegisterExtensions(ctx ocm.Context) error { pi := plugincacheattr.Get(ctx) + logger := Logger(ctx) for _, n := range pi.PluginNames() { p := pi.Get(n) if !p.IsValid() { @@ -33,12 +29,12 @@ func RegisterExtensions(ctx ocm.Context) error { for _, a := range p.GetDescriptor().Actions { h, err := plugin2.New(p, a.Name) if err != nil { - ctx.Logger(TAG).Error("cannot create action handler for plugin", "plugin", p.Name(), "handler", a.Name) + 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 { - ctx.Logger(TAG).LogError(err, "cannot register action handler for plugin", "plugin", p.Name(), "handler", a.Name, "selector", s) + logger.LogError(err, "cannot register action handler for plugin", "plugin", p.Name(), "handler", a.Name, "selector", s) } } } @@ -48,7 +44,7 @@ func RegisterExtensions(ctx ocm.Context) error { if m.Version != "" { name = name + runtime.VersionSeparator + m.Version } - ctx.Logger(TAG).Info("registering access method", + logger.Info("registering access method", "plugin", p.Name(), "type", name) pi.GetContext().AccessMethods().Register(name, access.NewType(name, p, &m)) @@ -59,9 +55,9 @@ func RegisterExtensions(ctx ocm.Context) error { if c.ContextType != "" && c.RepositoryType != "" && c.MediaType != "" { hdlr, err := plugin.New(p, u.Name, nil) if err != nil { - ctx.Logger(TAG).Error("cannot create blob handler for plugin", "plugin", p.Name(), "handler", u.Name) + logger.Error("cannot create blob handler fpr plugin", "plugin", p.Name(), "handler", u.Name) } else { - ctx.Logger(TAG).Info("registering repository blob handler", + logger.Info("registering repository blob handler", "context", c.ContextType+":"+c.RepositoryType, "plugin", p.Name(), "handler", u.Name) diff --git a/pkg/contexts/ocm/repositories/ctf/repo_test.go b/pkg/contexts/ocm/repositories/ctf/repo_test.go index c36db0f11..4e2d1a018 100644 --- a/pkg/contexts/ocm/repositories/ctf/repo_test.go +++ b/pkg/contexts/ocm/repositories/ctf/repo_test.go @@ -7,6 +7,7 @@ package ctf_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + . "github.com/open-component-model/ocm/pkg/finalizer" . "github.com/open-component-model/ocm/pkg/testutils" "github.com/mandelsoft/vfs/pkg/memoryfs" @@ -16,7 +17,6 @@ import ( "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/repositories/ctf" - "github.com/open-component-model/ocm/pkg/utils" ) const COMPONENT = "github.com/mandelsoft/ocm" @@ -31,7 +31,7 @@ var _ = Describe("access method", func() { }) It("adds component version", func() { - final := utils.Finalizer{} + final := Finalizer{} defer Defer(final.Finalize) a := Must(ctf.Create(ctx, accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, "ctf", 0o700, accessio.PathFileSystem(fs))) @@ -53,7 +53,7 @@ var _ = Describe("access method", func() { }) It("adds omits unadded new component version", func() { - final := utils.Finalizer{} + final := Finalizer{} defer Defer(final.Finalize) a := Must(ctf.Create(ctx, accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, "ctf", 0o700, accessio.PathFileSystem(fs))) diff --git a/pkg/contexts/ocm/resourcetypes/const.go b/pkg/contexts/ocm/resourcetypes/const.go index 7605912ff..cbb37d757 100644 --- a/pkg/contexts/ocm/resourcetypes/const.go +++ b/pkg/contexts/ocm/resourcetypes/const.go @@ -16,11 +16,21 @@ const ( // BLOB describes any anonymous untyped blob data. BLOB = "blob" // FILESYSTEM describes a directory structure stored as archive (tar, tgz). - FILESYSTEM = "filesystem" + FILESYSTEM = "directoryTree" + FILESYSTEM_LEGACY = "filesystem" // EXECUTABLE describes an OS executable. EXECUTABLE = "executable" // PLAIN_TEXT describes plain text. PLAIN_TEXT = "plainText" // OCM_PLUGIN describes an OS executable OCM plugin. OCM_PLUGIN = "ocmPlugin" + + // OCM_FILE describes a generic file or unspecified byte stream. + OCM_FILE = "file" + // OCM_YAML describes a YAML file. + OCM_YAML = "yaml" + // OCM_JSON describes a JSON file. + OCM_JSON = "json" + // OCM_XML describes a XML file. + OCM_XML = "xml" ) diff --git a/pkg/contexts/ocm/transfer/utils.go b/pkg/contexts/ocm/transfer/logging.go similarity index 63% rename from pkg/contexts/ocm/transfer/utils.go rename to pkg/contexts/ocm/transfer/logging.go index 25ebf6550..0b88e1795 100644 --- a/pkg/contexts/ocm/transfer/utils.go +++ b/pkg/contexts/ocm/transfer/logging.go @@ -8,18 +8,15 @@ import ( "github.com/mandelsoft/logging" "github.com/open-component-model/ocm/pkg/contexts/ocm" + ocmlog "github.com/open-component-model/ocm/pkg/logging" ) -var TAG = logging.NewTag("transfer") +var REALM = ocmlog.DefineSubRealm("OCM transfer handling", "transfer") type ContextProvider interface { GetContext() ocm.Context } -func LogInfo(c ContextProvider, msg string, keyValuePairs ...interface{}) { - c.GetContext().Logger(TAG).Info(msg, keyValuePairs...) -} - func Logger(c ContextProvider, keyValuePairs ...interface{}) logging.Logger { - return c.GetContext().Logger(TAG).WithValues(keyValuePairs...) + return c.GetContext().Logger(REALM).WithValues(keyValuePairs...) } diff --git a/pkg/contexts/ocm/transfer/transfer.go b/pkg/contexts/ocm/transfer/transfer.go index 1fe13d927..b07d1ab6a 100644 --- a/pkg/contexts/ocm/transfer/transfer.go +++ b/pkg/contexts/ocm/transfer/transfer.go @@ -86,7 +86,7 @@ func transferVersion(printer common.Printer, log logging.Logger, state WalkingSt 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) + 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()) } diff --git a/pkg/contexts/ocm/transfer/transferhandler/spiff/README.md b/pkg/contexts/ocm/transfer/transferhandler/spiff/README.md index 89776a018..acc1db6b0 100644 --- a/pkg/contexts/ocm/transfer/transferhandler/spiff/README.md +++ b/pkg/contexts/ocm/transfer/transferhandler/spiff/README.md @@ -25,6 +25,7 @@ following bindings: - `labels` *<map[string]>*: labels of the component version (deep) - `element` *<map>*: the meta data of the resource and the field `type` containing the element type. - `access` *<map>*: the access specification of the resource + - `target` *<map>*: the respository specification of the target resource The result value (field `process`) must be a boolean describing whether the resource should be transported ny-value. diff --git a/pkg/contexts/ocm/transfer/transferhandler/spiff/handler.go b/pkg/contexts/ocm/transfer/transferhandler/spiff/handler.go index 9d229bab9..b32c4d7a9 100644 --- a/pkg/contexts/ocm/transfer/transferhandler/spiff/handler.go +++ b/pkg/contexts/ocm/transfer/transferhandler/spiff/handler.go @@ -49,12 +49,12 @@ func (h *Handler) OverwriteVersion(src ocm.ComponentVersionAccess, tgt ocm.Compo return h.Handler.OverwriteVersion(src, tgt) } -func (h *Handler) TransferVersion(repo ocm.Repository, src ocm.ComponentVersionAccess, meta *compdesc.ComponentReference) (ocm.ComponentVersionAccess, transferhandler.TransferHandler, error) { +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) + return h.Handler.TransferVersion(repo, src, meta, tgt) } - binding := h.getBinding(src, nil, &meta.ElementMeta, nil) + 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 @@ -67,11 +67,11 @@ func (h *Handler) TransferVersion(repo ocm.Repository, src ocm.ComponentVersionA } } if s == nil { - return h.Handler.TransferVersion(repo, src, meta) + return h.Handler.TransferVersion(repo, src, meta, tgt) } opts := *h.opts opts.script = s - cv, _, err := h.Handler.TransferVersion(repo, src, meta) + cv, _, err := h.Handler.TransferVersion(repo, src, meta, tgt) return cv, &Handler{ Handler: h.Handler, opts: &opts, @@ -88,7 +88,7 @@ func (h *Handler) TransferResource(src ocm.ComponentVersionAccess, a ocm.AccessS if h.opts.GetScript() == nil { return true, nil } - binding := h.getBinding(src, a, &r.Meta().ElementMeta, &r.Meta().Type) + binding := h.getBinding(src, a, &r.Meta().ElementMeta, &r.Meta().Type, nil) return h.EvalBool("resource", binding, "process") } @@ -99,11 +99,11 @@ func (h *Handler) TransferSource(src ocm.ComponentVersionAccess, a ocm.AccessSpe if h.opts.GetScript() == nil { return true, nil } - binding := h.getBinding(src, a, &r.Meta().ElementMeta, &r.Meta().Type) + 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) map[string]interface{} { +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) @@ -116,6 +116,9 @@ func (h *Handler) getBinding(src ocm.ComponentVersionAccess, a ocm.AccessSpec, m if typ != nil { binding["element"].(map[string]interface{})["type"] = *typ } + if tgt != nil { + binding["target"] = getData(tgt.GetSpecification()) + } return binding } diff --git a/pkg/contexts/ocm/transfer/transferhandler/standard/handler.go b/pkg/contexts/ocm/transfer/transferhandler/standard/handler.go index 1a534154f..215bca67d 100644 --- a/pkg/contexts/ocm/transfer/transferhandler/standard/handler.go +++ b/pkg/contexts/ocm/transfer/transferhandler/standard/handler.go @@ -9,6 +9,7 @@ import ( "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/errors" ) type Handler struct { @@ -35,8 +36,13 @@ func (h *Handler) OverwriteVersion(src ocm.ComponentVersionAccess, tgt ocm.Compo return h.opts.IsOverwrite(), nil } -func (h *Handler) TransferVersion(repo ocm.Repository, src ocm.ComponentVersionAccess, meta *compdesc.ComponentReference) (ocm.ComponentVersionAccess, transferhandler.TransferHandler, error) { +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 @@ -55,11 +61,19 @@ func (h *Handler) TransferSource(src ocm.ComponentVersionAccess, a ocm.AccessSpe func (h *Handler) HandleTransferResource(r ocm.ResourceAccess, m ocm.AccessMethod, hint string, t ocm.ComponentVersionAccess) error { blob := accessio.BlobAccessForDataAccess("", -1, m.MimeType(), m) defer blob.Close() - return t.SetResourceBlob(r.Meta(), blob, hint, nil) + return t.SetResourceBlob(r.Meta(), blob, hint, h.GlobalAccess(t.GetContext(), m)) } func (h *Handler) HandleTransferSource(r ocm.SourceAccess, m ocm.AccessMethod, hint string, t ocm.ComponentVersionAccess) error { blob := accessio.BlobAccessForDataAccess("", -1, m.MimeType(), m) defer blob.Close() - return t.SetSourceBlob(r.Meta(), blob, hint, nil) + + 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 index 28a89c784..fb6855947 100644 --- a/pkg/contexts/ocm/transfer/transferhandler/standard/handler_test.go +++ b/pkg/contexts/ocm/transfer/transferhandler/standard/handler_test.go @@ -28,6 +28,7 @@ import ( "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/signing" @@ -91,7 +92,7 @@ var _ = Describe("Transfer handler", func() { env.Cleanup() }) - It("it should copy a resource by value to a ctf file", func() { + DescribeTable("it should copy a resource by value to a ctf file", func(acc string, topts ...transferhandler.TransferOption) { src, err := ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env) Expect(err).To(Succeed()) cv, err := src.LookupComponentVersion(COMPONENT, VERSION) @@ -101,6 +102,7 @@ var _ = Describe("Transfer handler", func() { defer tgt.Close() opts := &standard.Options{} opts.SetResourcesByValue(true) + transferhandler.ApplyOptions(opts, topts...) handler := standard.NewDefaultHandler(opts) //handler, err := standard.New(standard.ResourcesByValue()) Expect(err).To(Succeed()) @@ -119,7 +121,7 @@ var _ = Describe("Transfer handler", func() { fmt.Printf("%s\n", string(data)) hash := HashManifest1(artifactset.DefaultArtifactSetDescriptorFileName) - Expect(string(data)).To(StringEqualWithContext("{\"localReference\":\"" + hash + "\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\"" + OCINAMESPACE + ":" + OCIVERSION + "\",\"type\":\"localBlob\"}")) + Expect(string(data)).To(StringEqualWithContext(fmt.Sprintf(acc, hash))) r, err := comp.GetResourceByIndex(1) Expect(err).To(Succeed()) @@ -138,7 +140,13 @@ var _ = Describe("Transfer handler", func() { data, err = blob.Get() Expect(err).To(Succeed()) Expect(string(data)).To(Equal("manifestlayer")) - }) + }, + Entry("without preserve global", + "{\"localReference\":\"%s\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\""+OCINAMESPACE+":"+OCIVERSION+"\",\"type\":\"localBlob\"}"), + 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\"}", + standard.KeepGlobalAccess()), + ) It("it should use additional resolver to resolve component ref", func() { parentSrc, err := ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH2, 0, env) diff --git a/pkg/contexts/ocm/transfer/transferhandler/standard/options.go b/pkg/contexts/ocm/transfer/transferhandler/standard/options.go index aef520626..ac7254f98 100644 --- a/pkg/contexts/ocm/transfer/transferhandler/standard/options.go +++ b/pkg/contexts/ocm/transfer/transferhandler/standard/options.go @@ -15,6 +15,8 @@ type Options struct { recursive bool resourcesByValue bool sourcesByValue bool + keepGlobalAccess bool + stopOnExisting bool overwrite bool resolver ocm.ComponentVersionResolver } @@ -24,48 +26,65 @@ var ( _ SourcesByValueOption = (*Options)(nil) _ RecursiveOption = (*Options)(nil) _ ResolverOption = (*Options)(nil) + _ KeepGlobalAccessOption = (*Options)(nil) ) func (o *Options) SetOverwrite(overwrite bool) { o.overwrite = overwrite } +func (o *Options) IsOverwrite() bool { + return o.overwrite +} + func (o *Options) SetRecursive(recursive bool) { o.recursive = recursive } +func (o *Options) IsRecursive() bool { + return o.recursive +} + func (o *Options) SetResourcesByValue(resourcesByValue bool) { o.resourcesByValue = resourcesByValue } -func (o *Options) SetSourcesByValue(sourcesByValue bool) { - o.sourcesByValue = sourcesByValue +func (o *Options) IsResourcesByValue() bool { + return o.resourcesByValue } -func (o *Options) SetResolver(resolver ocm.ComponentVersionResolver) { - o.resolver = resolver +func (o *Options) SetSourcesByValue(sourcesByValue bool) { + o.sourcesByValue = sourcesByValue } -func (o *Options) IsOverwrite() bool { - return o.overwrite +func (o *Options) IsSourcesByValue() bool { + return o.sourcesByValue } -func (o *Options) IsRecursive() bool { - return o.recursive +func (o *Options) SetKeepGlobalAccess(keepGlobalAccess bool) { + o.keepGlobalAccess = keepGlobalAccess } -func (o *Options) IsResourcesByValue() bool { - return o.resourcesByValue +func (o *Options) IsKeepGlobalAccess() bool { + return o.keepGlobalAccess } -func (o *Options) IsSourcesByValue() bool { - return o.sourcesByValue +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 o.stopOnExisting +} + /////////////////////////////////////////////////////////////////////////////// type OverwriteOption interface { @@ -195,3 +214,55 @@ func Resolver(resolver ocm.ComponentVersionResolver) transferhandler.TransferOpt resolver: resolver, } } + +/////////////////////////////////////////////////////////////////////////////// + +type KeepGlobalAccessOption interface { + SetKeepGlobalAccess(bool) + IsKeepGlobalAccess() bool +} + +type keepGlobalOption struct { + 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("resolver") + } +} + +func KeepGlobalAccess(args ...bool) transferhandler.TransferOption { + return &keepGlobalOption{ + flag: utils.GetOptionFlag(args...), + } +} + +/////////////////////////////////////////////////////////////////////////////// + +type StopOnExistingVersionOption interface { + SetStopOnExistingVersion(bool) + IsStopOnExistingVersion() bool +} + +type stopOnExistingVersionOption struct { + 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("sources by-value") + } +} + +func StopOnExistingVersion(args ...bool) transferhandler.TransferOption { + return &stopOnExistingVersionOption{ + flag: utils.GetOptionFlag(args...), + } +} diff --git a/pkg/contexts/ocm/transfer/transferhandler/transferhandler.go b/pkg/contexts/ocm/transfer/transferhandler/transferhandler.go index 61cc53e20..cf2b0b0c9 100644 --- a/pkg/contexts/ocm/transfer/transferhandler/transferhandler.go +++ b/pkg/contexts/ocm/transfer/transferhandler/transferhandler.go @@ -19,7 +19,7 @@ type TransferOption interface { type TransferHandler interface { OverwriteVersion(src ocm.ComponentVersionAccess, tgt ocm.ComponentVersionAccess) (bool, error) - TransferVersion(repo ocm.Repository, src ocm.ComponentVersionAccess, meta *compdesc.ComponentReference) (ocm.ComponentVersionAccess, TransferHandler, error) + TransferVersion(repo ocm.Repository, src ocm.ComponentVersionAccess, meta *compdesc.ComponentReference, tgt ocm.Repository) (ocm.ComponentVersionAccess, TransferHandler, error) TransferResource(src ocm.ComponentVersionAccess, a ocm.AccessSpec, r ocm.ResourceAccess) (bool, error) TransferSource(src ocm.ComponentVersionAccess, a ocm.AccessSpec, r ocm.SourceAccess) (bool, error) diff --git a/pkg/contexts/ocm/transfer/transferrepo.go b/pkg/contexts/ocm/transfer/transferrepo.go index a7ace500b..1f6d6bc5a 100644 --- a/pkg/contexts/ocm/transfer/transferrepo.go +++ b/pkg/contexts/ocm/transfer/transferrepo.go @@ -50,7 +50,7 @@ func transferVersions(printer common.Printer, closure TransportClosure, list *er if list.Addf(subp, err, "list versions for %s", c) == nil { for _, v := range vers { ref := compdesc.NewComponentReference("", c, v, nil) - sub, h, err := handler.TransferVersion(repo, nil, ref) + sub, h, err := handler.TransferVersion(repo, nil, ref, tgt) if list.Addf(subp, err, "version %s", v) != nil { continue } diff --git a/pkg/contexts/ocm/utils/resourceref.go b/pkg/contexts/ocm/utils/resourceref.go index 0ef70cc92..b0c45014c 100644 --- a/pkg/contexts/ocm/utils/resourceref.go +++ b/pkg/contexts/ocm/utils/resourceref.go @@ -7,11 +7,12 @@ package utils import ( "fmt" + . "github.com/open-component-model/ocm/pkg/finalizer" + "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/errors" - "github.com/open-component-model/ocm/pkg/utils" ) func ResolveReferencePath(cv ocm.ComponentVersionAccess, path []metav1.Identity, resolver ocm.ComponentVersionResolver) (ocm.ComponentVersionAccess, error) { @@ -23,7 +24,7 @@ func ResolveReferencePath(cv ocm.ComponentVersionAccess, path []metav1.Identity, return nil, errors.Wrapf(err, "component version already closed") } - var final utils.Finalizer + var final Finalizer defer final.Finalize() for _, cr := range path { diff --git a/pkg/errors/error.go b/pkg/errors/error.go index 29d8da59c..b3cf2d598 100644 --- a/pkg/errors/error.go +++ b/pkg/errors/error.go @@ -92,6 +92,20 @@ type wrappedError struct { 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 { return nil @@ -105,6 +119,17 @@ func Wrapf(err error, msg string, args ...interface{}) error { } } +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) } diff --git a/pkg/exception/exception.go b/pkg/exception/exception.go new file mode 100644 index 000000000..6509adb77 --- /dev/null +++ b/pkg/exception/exception.go @@ -0,0 +1,324 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.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/open-component-model/ocm/pkg/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 { + if e, ok := r.(*exception); ok && match(e.err, matchers...) { + *errp = e.err + } else { + panic(r) + } + } +} + +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 +} + +// 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 new file mode 100644 index 000000000..8de551471 --- /dev/null +++ b/pkg/exception/exception_test.go @@ -0,0 +1,119 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package exception_test + +import ( + "fmt" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/open-component-model/ocm/pkg/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 new file mode 100644 index 000000000..18232f1c9 --- /dev/null +++ b/pkg/exception/suite_test.go @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.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/utils/finalizer.go b/pkg/finalizer/finalizer.go similarity index 59% rename from pkg/utils/finalizer.go rename to pkg/finalizer/finalizer.go index 0fa7ddbd6..26ff15fa1 100644 --- a/pkg/utils/finalizer.go +++ b/pkg/finalizer/finalizer.go @@ -2,13 +2,14 @@ // // SPDX-License-Identifier: Apache-2.0 -package utils +package finalizer import ( "io" "sync" "github.com/open-component-model/ocm/pkg/errors" + "github.com/open-component-model/ocm/pkg/exception" ) // Finalizer gathers finalization functions and calls @@ -22,10 +23,22 @@ import ( // a loop block. type Finalizer struct { lock sync.Mutex + catch exception.Matcher pending []func() error nested *Finalizer } +// 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) *Finalizer { @@ -36,7 +49,7 @@ func (f *Finalizer) Lock(locker sync.Locker) *Finalizer { // WithVoid registers a simple function to be // called on finalization. func (f *Finalizer) WithVoid(fi func()) *Finalizer { - return f.With(func() error { fi(); return nil }) + return f.With(CallingV(fi)) } func (f *Finalizer) With(fi func() error) *Finalizer { @@ -49,6 +62,56 @@ func (f *Finalizer) With(fi func() error) *Finalizer { 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) *Finalizer { @@ -103,7 +166,20 @@ func (f *Finalizer) Length() int { // 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) { - errors.PropagateError(efferr, f.Finalize) + 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 { + *efferr = errors.ErrList().Add(e).Add(*efferr).Result() + } else { + panic(r) + } + } } // FinalizeWithErrorPropagationf calls all finalizations in the reverse order of @@ -113,15 +189,43 @@ func (f *Finalizer) FinalizeWithErrorPropagation(efferr *error) { // 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{}) { - errors.PropagateErrorf(efferr, f.Finalize, msg, args...) + 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 { + *efferr = errors.ErrList().Add(e).Add(*efferr).Result() + } else { + panic(r) + } + } } // Finalize calls all finalizations in the reverse order of -// their registration. -func (f *Finalizer) Finalize() error { +// 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 { + err = errors.ErrList().Add(e).Add(err).Result() + } else { + panic(r) + } + } + return err +} + +func (f *Finalizer) finalize() (err error) { list := errors.ErrList() l := len(f.pending) for i := range f.pending { diff --git a/pkg/utils/finalizer_test.go b/pkg/finalizer/finalizer_test.go similarity index 76% rename from pkg/utils/finalizer_test.go rename to pkg/finalizer/finalizer_test.go index dc174a2d8..36053f92b 100644 --- a/pkg/utils/finalizer_test.go +++ b/pkg/finalizer/finalizer_test.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package utils_test +package finalizer_test import ( "fmt" @@ -10,25 +10,44 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/open-component-model/ocm/pkg/utils" + "github.com/open-component-model/ocm/pkg/exception" + "github.com/open-component-model/ocm/pkg/finalizer" ) type Order []string func F(n string, order *Order) func() error { return func() error { - *order = append(*order, n) - return nil + return R(n, order) } } +func R(n string, order *Order) error { + *order = append(*order, n) + return nil +} + var _ = Describe("finalizer", func() { - It("finalize in revered order", func() { - var finalize utils.Finalizer + It("finalize with arbitrary method", func() { + var finalize finalizer.Finalizer + var order Order + + finalize.With(finalizer.Calling2(R, "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() @@ -37,7 +56,7 @@ var _ = Describe("finalizer", func() { }) It("is reusable after calling Finalize", func() { - var finalize utils.Finalizer + var finalize finalizer.Finalizer var order Order finalize.With(F("A", &order)) @@ -56,7 +75,7 @@ var _ = Describe("finalizer", func() { }) It("separately finalizes a Nested finalizer", func() { - var finalize utils.Finalizer + var finalize finalizer.Finalizer var order Order finalize.With(F("A", &order)) @@ -87,7 +106,7 @@ var _ = Describe("finalizer", func() { }) It("separately finalizes new finalizers", func() { - var finalize utils.Finalizer + var finalize finalizer.Finalizer var order Order finalize.With(F("A", &order)) @@ -164,6 +183,25 @@ var _ = Describe("finalizer", func() { }) }) }) + + 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")) + }) + }) }) func errfunc(succeed bool) func() error { @@ -174,7 +212,7 @@ func errfunc(succeed bool) func() error { } func testFunc(msg string, err error, succeed bool) (efferr error) { - var finalize utils.Finalizer + var finalize finalizer.Finalizer defer finalize.FinalizeWithErrorPropagationf(&efferr, msg) finalize.With(errfunc(succeed)) diff --git a/pkg/finalizer/suite_test.go b/pkg/finalizer/suite_test.go new file mode 100644 index 000000000..e721838b2 --- /dev/null +++ b/pkg/finalizer/suite_test.go @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.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/logging/config_test.go b/pkg/logging/config_test.go index 72f4e29c3..8b62c82d3 100644 --- a/pkg/logging/config_test.go +++ b/pkg/logging/config_test.go @@ -6,7 +6,6 @@ package logging_test import ( "bytes" - "encoding/json" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -43,11 +42,9 @@ ERROR ocm error `)) }) It("just logs with config", func() { - data := `{ "rule": { "level": "Debug" } }` + r := logcfg.ConditionalRule("debug") cfg := &logcfg.Config{ - Rules: []json.RawMessage{ - []byte(data), - }, + Rules: []logcfg.Rule{r}, } Expect(local.Configure(cfg)).To(Succeed()) diff --git a/pkg/logging/logging.go b/pkg/logging/logging.go index 25df04db7..3d4163adf 100644 --- a/pkg/logging/logging.go +++ b/pkg/logging/logging.go @@ -18,7 +18,7 @@ import ( // REALM is used to tag all logging done by this library with the ocm tag. // This is also used as message context to configure settings for all // log output provided by this library. -var REALM = logging.NewRealm("ocm") +var REALM = logging.DefineRealm("ocm", "general realm used for the ocm go library.") type StaticContext struct { logging.Context @@ -61,9 +61,14 @@ func (s *StaticContext) Configure(config *logcfg.Config, extra ...string) error return logcfg.Configure(logContext, config) } -var logContext = NewContext(nil) +// global is a wrapper for the default global log content. +var global = NewContext(nil) -// SetContext sets a new precondigure context. +// logContext is the global ocm log context. +// It can be replaced by SetContext. +var logContext = global + +// SetContext sets a new preconfigured context. // This function should be called prior to any configuration // to avoid loosing them. func SetContext(ctx logging.Context) { @@ -86,3 +91,14 @@ func Logger(messageContext ...logging.MessageContext) logging.Logger { func Configure(config *logcfg.Config, extra ...string) error { return logContext.Configure(config, extra...) } + +// ConfigureGlobal applies configuration for the default global log context +// provided by this package. +func ConfigureGlobal(config *logcfg.Config, extra ...string) error { + return global.Configure(config, extra...) +} + +// DynamicLogger gets an unbound logger based on the default library logging context. +func DynamicLogger(messageContext ...logging.MessageContext) logging.UnboundLogger { + return logging.DynamicLogger(Context(), messageContext...) +} diff --git a/pkg/logging/stdkeys.go b/pkg/logging/stdkeys.go new file mode 100644 index 000000000..74b36add2 --- /dev/null +++ b/pkg/logging/stdkeys.go @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package logging + +// standard logging value attribute names used in this library. +const ( + ATTR_HOST = "host" + ATTR_USER = "user" + ATTR_PATH = "path" + ATTR_NAMESPACE = "namespace" +) diff --git a/pkg/logging/utils.go b/pkg/logging/utils.go index 225196ee7..887dc9e5b 100644 --- a/pkg/logging/utils.go +++ b/pkg/logging/utils.go @@ -4,6 +4,12 @@ package logging +import ( + "path" + + "github.com/mandelsoft/logging" +) + func ErrorMessage(err error) *string { if err == nil { return nil @@ -11,3 +17,11 @@ func ErrorMessage(err error) *string { m := err.Error() return &m } + +func SubRealm(names ...string) logging.Realm { + return logging.NewRealm(path.Join(REALM.Name(), path.Join(names...))) +} + +func DefineSubRealm(desc string, names ...string) logging.Realm { + return logging.DefineRealm(path.Join(REALM.Name(), path.Join(names...)), desc) +} diff --git a/pkg/testutils/utils.go b/pkg/testutils/utils.go index e9cc905e3..005eac193 100644 --- a/pkg/testutils/utils.go +++ b/pkg/testutils/utils.go @@ -10,6 +10,8 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + + "github.com/open-component-model/ocm/pkg/utils" ) func Close(c io.Closer, msg ...interface{}) { @@ -35,9 +37,27 @@ func Must[T any](o T, err error) T { return o } -func MustWithOffset[T any](offset int, o T, err error) T { - ExpectWithOffset(offset+1, err).To(Succeed()) - return o +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 { diff --git a/pkg/toi/drivers/docker/driver.go b/pkg/toi/drivers/docker/driver.go index ce93598f6..8bbc623a4 100644 --- a/pkg/toi/drivers/docker/driver.go +++ b/pkg/toi/drivers/docker/driver.go @@ -24,10 +24,10 @@ import ( "github.com/docker/docker/pkg/stdcopy" "github.com/docker/docker/registry" "github.com/mitchellh/copystructure" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" "github.com/open-component-model/ocm/pkg/common/accessio" + "github.com/open-component-model/ocm/pkg/errors" + "github.com/open-component-model/ocm/pkg/toi" "github.com/open-component-model/ocm/pkg/toi/install" ) @@ -295,10 +295,10 @@ func (d *Driver) Exec(op *install.Operation) (*install.OperationResult, error) { } if s.Error != nil { opResult, fetchErr := d.fetchOutputs(ctx, resp.ID, op) - return opResult, containerError(fmt.Sprintf("container exit code: %d, message", s.StatusCode), err, fetchErr) + 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, message", s.StatusCode), err, fetchErr) + return opResult, containerError(fmt.Sprintf("container exit code: %d", s.StatusCode), err, fetchErr) } opResult, fetchErr := d.fetchOutputs(ctx, resp.ID, op) if fetchErr != nil { @@ -357,9 +357,9 @@ func (d *Driver) setConfigurationOptions(op *install.Operation) error { func containerError(containerMessage string, containerErr, fetchErr error) error { if fetchErr != nil { - return fmt.Errorf("%s: %v. fetching outputs failed: %w", containerMessage, containerErr, fetchErr) + return errors.NewEf(containerErr, "%s: %v. fetching outputs failed", containerMessage, fetchErr) } - return fmt.Errorf("%s: %w", containerMessage, containerErr) + return errors.NewEf(containerErr, "%s", containerMessage) } // fetchOutputs takes a context and a container ID; it copies the PathOutputs directory from that container. @@ -425,7 +425,7 @@ func generateTar(files map[string]accessio.BlobAccess, uid int) (io.ReadCloser, var err error wg := sync.WaitGroup{} wg.Add(1) - logrus.Infof("waiting for successful data transfer...\n") + toi.Log.Info("waiting for successful data transfer") go func() { defer wg.Done() defer w.Close() @@ -433,7 +433,7 @@ func generateTar(files map[string]accessio.BlobAccess, uid int) (io.ReadCloser, have := map[string]bool{} for path, content := range files { path = unix_path.Join(install.PathInputs, path) - logrus.Infof("transferring %s...\n", 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 != "/" { @@ -462,7 +462,7 @@ func generateTar(files map[string]accessio.BlobAccess, uid int) (io.ReadCloser, tw.WriteHeader(fildHdr) reader, e := content.Reader() if e != nil { - logrus.Infof("cannot transfer %s: %s\n", path, e) + toi.Log.LogError(err, "cannot transfer", "path", path) err = e return } @@ -500,7 +500,7 @@ func (d *Driver) inspectImage(ctx context.Context, image string) (types.ImageIns // validateImageDigest validates the operation image digest, if exists, against // the supplied repoDigests. -func (d *Driver) validateImageDigest(image install.Image, repoDigests []string) error { +func (d *Driver) validateImageDigest(image toi.Image, repoDigests []string) error { if image.Digest == "" { return nil } diff --git a/pkg/toi/install/action.go b/pkg/toi/install/action.go index e78088b0d..874112b77 100644 --- a/pkg/toi/install/action.go +++ b/pkg/toi/install/action.go @@ -9,26 +9,32 @@ import ( "fmt" "sort" + . "github.com/open-component-model/ocm/pkg/finalizer" + "github.com/ghodss/yaml" "github.com/mandelsoft/vfs/pkg/osfs" "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/sirupsen/logrus" "github.com/xeipuuv/gojsonschema" "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/config" + "github.com/open-component-model/ocm/pkg/contexts/config" + globalconfig "github.com/open-component-model/ocm/pkg/contexts/config/config" + "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/errors" "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" ) @@ -65,8 +71,8 @@ func ValidateByScheme(src []byte, schemedata []byte) error { } type ExecutorContext struct { - Spec ExecutorSpecification - Image *Image + Spec toi.ExecutorSpecification + Image *toi.Image CV ocm.ComponentVersionAccess } @@ -83,7 +89,7 @@ func GetResource(res ocm.ResourceAccess, target interface{}) error { return runtime.DefaultYAMLEncoding.Unmarshal(data, target) } -func DetermineExecutor(executor *Executor, octx ocm.Context, cv ocm.ComponentVersionAccess, resolver ocm.ComponentVersionResolver) (*ExecutorContext, error) { +func DetermineExecutor(executor *toi.Executor, octx ocm.Context, cv ocm.ComponentVersionAccess, resolver ocm.ComponentVersionResolver) (*ExecutorContext, error) { espec := ExecutorContext{Image: executor.Image} if espec.Image == nil { @@ -104,7 +110,7 @@ func DetermineExecutor(executor *Executor, octx ocm.Context, cv ocm.ComponentVer }() switch res.Meta().Type { case resourcetypes.OCI_IMAGE: - case TypeTOIExecutor: + case toi.TypeTOIExecutor: err := GetResource(res, &espec.Spec) if err != nil { return nil, errors.ErrInvalidWrap(err, "toi executor") @@ -135,7 +141,7 @@ func DetermineExecutor(executor *Executor, octx ocm.Context, cv ocm.ComponentVer if err != nil { return nil, errors.Wrapf(err, "image ref for executor resource %s not found", executor.ResourceRef.String()) } - espec.Image = &Image{ + espec.Image = &toi.Image{ Ref: ref, } espec.CV, eff = eff, nil @@ -156,7 +162,7 @@ func mappingKeyFor(value string, m map[string]string) string { } // CheckCredentialRequests determine required credentials for executor. -func CheckCredentialRequests(executor *Executor, spec *PackageSpecification, espec *ExecutorSpecification) (map[string]CredentialsRequestSpec, map[string]string, error) { +func CheckCredentialRequests(executor *toi.Executor, spec *toi.PackageSpecification, espec *toi.ExecutorSpecification) (map[string]CredentialsRequestSpec, map[string]string, error) { credentials := spec.Credentials credmapping := map[string]string{} @@ -265,7 +271,7 @@ func ProcessConfig(name string, octx ocm.Context, cv ocm.ComponentVersionAccess, } } if len(schemedata) > 0 { - logrus.Infof("validating %s by scheme...", name) + toi.Log.Info("validating by scheme", name) err = ValidateByScheme(config, schemedata) if err != nil { return nil, errors.Wrapf(err, name+" validation failed") @@ -280,10 +286,14 @@ func ProcessConfig(name string, octx ocm.Context, cv ocm.ComponentVersionAccess, return config, err } -func ExecuteAction(p common.Printer, d Driver, name string, spec *PackageSpecification, creds *Credentials, params []byte, octx ocm.Context, cv ocm.ComponentVersionAccess, resolver ocm.ComponentVersionResolver) (*OperationResult, error) { +// ExecuteAction prepared the execution options and executes the action. +func ExecuteAction(p common.Printer, d Driver, name string, spec *toi.PackageSpecification, creds *Credentials, params []byte, octx ocm.Context, cv ocm.ComponentVersionAccess, resolver ocm.ComponentVersionResolver) (*OperationResult, error) { var err error - var executor *Executor + var finalize Finalizer + defer finalize.Finalize() + + var executor *toi.Executor for idx, e := range spec.Executors { if e.Actions == nil { executor = &spec.Executors[idx] @@ -339,7 +349,7 @@ func ExecuteAction(p common.Printer, d Driver, name string, spec *PackageSpecifi if econfig == nil { p.Printf("no executor config found\n") } else { - p.Printf("using executor config %s\n", utils2.IndentLines(string(econfig), " ")) + p.Printf("using executor config:\n%s\n", utils2.IndentLines(string(econfig), " ")) } // handle credentials credentials, credmapping, err := CheckCredentialRequests(executor, spec, &espec.Spec) @@ -347,16 +357,27 @@ func ExecuteAction(p common.Printer, d Driver, name string, spec *PackageSpecifi return nil, err } - // prepare ocm config with credential settings - ccfg := config.New() + // prepare ocm config with credential settings and logging config forwarding if len(credentials) > 0 { if creds == nil { return nil, errors.Newf("credential settings required") } - ccfg, err = GetCredentials(octx.CredentialsContext(), creds, credentials, credmapping) + } + ccfg, err := GetCredentials(octx.CredentialsContext(), creds, credentials, credmapping) + if err != nil { + return nil, errors.Wrapf(err, "credential evaluation failed") + } + + if lc := logforward.Get(octx); lc != nil { + g, err := config.ToGenericConfig(logcfg.NewWithConfig("default", lc)) if err != nil { - return nil, errors.Wrapf(err, "credential evaluation failed") + return nil, errors.Wrapf(err, "cannot create logging config forwarding") } + ccfg.Configurations = append(ccfg.Configurations, g) + } + { + data, _ := yaml.Marshal(ccfg) + p.Printf("using ocm config:\n%s\n", utils2.IndentLines(string(data), " ")) } // prepare user config @@ -367,7 +388,7 @@ func ExecuteAction(p common.Printer, d Driver, name string, spec *PackageSpecifi if params == nil { p.Printf("no parameter config found\n") } else { - p.Printf("using package parameters %s\n", utils2.IndentLines(string(params), " ")) + p.Printf("using package parameters:\n:%s\n", utils2.IndentLines(string(params), " ")) } if executor.ParameterMapping != nil { @@ -378,7 +399,7 @@ func ExecuteAction(p common.Printer, d Driver, name string, spec *PackageSpecifi if err == nil { if !bytes.Equal(orig, params) { - p.Printf("using executor parameters %s\n", utils2.IndentLines(string(params), " ")) + p.Printf("using executor parameters:\n%s\n", utils2.IndentLines(string(params), " ")) } } } @@ -393,6 +414,8 @@ func ExecuteAction(p common.Printer, d Driver, name string, spec *PackageSpecifi } sort.Strings(names) p.Printf("using executor image %s[%s] with credentials %v\n", espec.Image.Ref, executor.ResourceRef.String(), names) + + // setup executor operation op := &Operation{ Action: name, Image: *espec.Image, @@ -403,11 +426,30 @@ func ExecuteAction(p common.Printer, d Driver, name string, spec *PackageSpecifi Err: nil, } + // prepare file content to be passed to executor + err = setupFiles(octx, &finalize, op, ccfg, params, econfig, cv) + if err != nil { + return nil, errors.Wrapf(err, "error setting up executor file set") + } + + op.Outputs = executor.Outputs + + // no config so far + err = d.SetConfig(map[string]string{}) + if err != nil { + return nil, err + } + op.ComponentVersion = common.VersionedElementKey(cv).String() + return d.Exec(op) +} + +func setupFiles(octx ocm.Context, finalize *Finalizer, op *Operation, ccfg *globalconfig.Config, params []byte, econfig []byte, cv ocm.ComponentVersionAccess) error { + // prepare file content to be passed to executor op.Files = map[string]accessio.BlobAccess{} if ccfg != nil { data, err := runtime.DefaultYAMLEncoding.Marshal(ccfg) if err != nil { - return nil, errors.Wrapf(err, "marshalling ocm config failed") + return errors.Wrapf(err, "marshalling ocm config failed") } op.Files[InputOCMConfig] = accessio.BlobAccessForData(mime.MIME_OCTET, data) } @@ -420,26 +462,23 @@ func ExecuteAction(p common.Printer, d Driver, name string, spec *PackageSpecifi if cv != nil { fs, err := osfs.NewTempFileSystem() if err != nil { - return nil, errors.Wrapf(err, "cannot create temp file system") + return errors.Wrapf(err, "cannot create temp file system") } - defer vfs.Cleanup(fs) + finalize.With(func() error { return vfs.Cleanup(fs) }) repo, err := ctf.Create(octx, accessobj.ACC_CREATE, "arch", 0o600, accessio.FormatTGZ, accessio.PathFileSystem(fs)) if err != nil { - return nil, errors.Wrapf(err, "cannot create repo for component version") + return errors.Wrapf(err, "cannot create repo for component version") + } + defer repo.Close() + handler, err := standard.New(standard.Recursive(), standard.KeepGlobalAccess()) + if err != nil { + return errors.Wrapf(err, "cannot create transfer handler") } - err = transfer.TransferVersion(nil, nil, cv, repo, nil) - repo.Close() + err = transfer.TransferVersion(nil, nil, cv, repo, handler) if err != nil { - return nil, errors.Wrapf(err, "component version transport failed") + return errors.Wrapf(err, "component version transport failed") } op.Files[InputOCMRepo] = accessio.BlobAccessForFile(mime.MIME_OCTET, "arch", fs) } - op.Outputs = executor.Outputs - - err = d.SetConfig(map[string]string{}) - if err != nil { - return nil, err - } - op.ComponentVersion = common.VersionedElementKey(cv).String() - return d.Exec(op) + return nil } diff --git a/pkg/toi/install/bundle/spec.go b/pkg/toi/install/bundle/spec.go index 6a10df5cf..163c23e72 100644 --- a/pkg/toi/install/bundle/spec.go +++ b/pkg/toi/install/bundle/spec.go @@ -8,6 +8,7 @@ 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" ) @@ -23,7 +24,7 @@ type BundleSpecification struct { type InstallationSpecification struct { ResourceRef *metav1.ResourceReference `json:"resourceRef,omitempty"` - Image *install.Image `json:"image,omitempty"` + Image *toi.Image `json:"image,omitempty"` Actions map[string]string `json:"actions,omitempty"` Required map[string]string `json:"required,omitempty"` diff --git a/pkg/toi/install/credentials.go b/pkg/toi/install/credentials.go index 4b7b7ae03..23185ca9b 100644 --- a/pkg/toi/install/credentials.go +++ b/pkg/toi/install/credentials.go @@ -5,8 +5,6 @@ package install import ( - "fmt" - "github.com/mandelsoft/spiff/features" "github.com/mandelsoft/spiff/spiffing" @@ -14,65 +12,21 @@ import ( 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/cpi" + "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/errors" "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 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"` -} - -type CredentialSpec struct { - // ConsumerId specifies the consumer id to look for the crentials - ConsumerId credentials.ConsumerIdentity `json:"consumerId,omitempty"` - // 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,omitempty"` - - // TargetConsumerId specifies the consumer id to feed with this crednetials - TargetConsumerId credentials.ConsumerIdentity `json:"targetConsumerId,omitempty"` -} +type ( + Credentials = toi.Credentials + CredentialSpec = toi.CredentialSpec + CredentialsRequest = toi.CredentialsRequest + CredentialsRequestSpec = toi.CredentialsRequestSpec +) func ParseCredentialSpecification(data []byte, desc string) (*Credentials, error) { spiff := spiffing.New().WithFeatures(features.CONTROL, features.INTERPOLATION) @@ -136,7 +90,7 @@ func GetCredentials(ctx credentials.Context, spec *Credentials, req map[string]C mapped = mapping[n] } if mapped == "" { - return nil, errors.Newf("mapping missing crednetial %q", n) + return nil, errors.Newf("mapping missing credential %q", n) } err = mem.AddCredentials(mapped, creds) if err != nil { @@ -158,6 +112,29 @@ func GetCredentials(ctx credentials.Context, spec *Credentials, req map[string]C } } } + for _, r := range spec.Forwarded { + if len(r.ConsumerId) == 0 { + return 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, errors.ErrNotFoundWrap(err, "consumer", r.ConsumerId.String()) + } + if src == nil { + return nil, errors.ErrNotFoundWrap(err, "consumer", r.ConsumerId.String()) + } + creds, err := src.Credentials(ctx) + if err != nil { + return 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) @@ -183,7 +160,7 @@ func evaluate(ctx credentials.Context, spec *CredentialSpec) (common.Properties, } if spec.ConsumerId != nil { cnt++ - match, _ := ctx.ConsumerIdentityMatchers().Get(credentials.ID_TYPE) + match, _ := ctx.ConsumerIdentityMatchers().Get(spec.ConsumerType) if match == nil { match = credentials.PartialMatch } diff --git a/pkg/toi/install/credentials_test.go b/pkg/toi/install/credentials_test.go index b5e3d3d89..e38d86d04 100644 --- a/pkg/toi/install/credentials_test.go +++ b/pkg/toi/install/credentials_test.go @@ -9,14 +9,23 @@ import ( . "github.com/onsi/gomega" . "github.com/open-component-model/ocm/pkg/testutils" + "github.com/open-component-model/ocm/pkg/common" "github.com/open-component-model/ocm/pkg/contexts/config" "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/credentials/repositories/directcreds" "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/memory" + "github.com/open-component-model/ocm/pkg/contexts/oci/identity" "github.com/open-component-model/ocm/pkg/runtime" "github.com/open-component-model/ocm/pkg/toi/install" ) var _ = Describe("credential mapping", func() { + consumerid := credentials.NewConsumerIdentity("CT", identity.ID_HOSTNAME, "github.com", identity.ID_PATHPREFIX, "mandelsoft") + ccreds := common.Properties{ + "user": "mandelsoft", + "pass": "mypass", + } memspec := memory.NewRepositorySpec("default") memcred := credentials.DirectCredentials{ "username": "mandelsoft", @@ -42,11 +51,22 @@ configurations: identity: name: target type: KubernetesCLuster + - credentials: + - credentialsName: Credentials + properties: + pass: mypass + user: mandelsoft + type: Credentials + identity: + hostname: github.com + pathprefix: mandelsoft/testrepo + type: CT type: credentials.config.ocm.software type: generic.config.ocm.software ` It("creates config data", FlakeAttempts(50), func() { ctx := credentials.New() + ctx.SetCredentialsForConsumer(consumerid, directcreds.NewCredentials(ccreds)) mem, err := ctx.RepositoryForSpec(memory.NewRepositorySpec("memory")) Expect(err).To(Succeed()) @@ -75,6 +95,12 @@ credentials: credentialsName: configured type: Memory repoName: memory +forwardedConsumers: +- consumerId: + type: CT + hostname: github.com + pathprefix: mandelsoft/testrepo + consumerType: hostpath ` spec, err := install.ParseCredentialSpecification([]byte(input), "settings") Expect(err).To(Succeed()) @@ -98,5 +124,10 @@ credentials: "token": "XXX", })) Expect(mem.LookupCredentials("other")).To(Equal(memcred)) + + creq := consumerid.Copy() + creq[identity.ID_PATHPREFIX] = "mandelsoft/testrepo/bla" + props := Must(credentials.CredentialsForConsumer(ctx, creq, hostpath.Matcher)) + Expect(props.Properties()).To(Equal(ccreds)) }) }) diff --git a/pkg/toi/install/execute.go b/pkg/toi/install/execute.go index 28d0bedd0..85935cac9 100644 --- a/pkg/toi/install/execute.go +++ b/pkg/toi/install/execute.go @@ -11,6 +11,7 @@ import ( 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/errors" + "github.com/open-component-model/ocm/pkg/toi" ) func Execute(p common.Printer, d Driver, name string, rid metav1.Identity, credsrc accessio.DataSource, paramsrc accessio.DataSource, octx ocm.Context, cv ocm.ComponentVersionAccess, resolver ocm.ComponentVersionResolver) (*OperationResult, error) { @@ -35,16 +36,16 @@ func Execute(p common.Printer, d Driver, name string, rid metav1.Identity, creds } } - ires, _, err := utils.MatchResourceReference(cv, TypeTOIPackage, metav1.NewResourceRef(rid), nil) + ires, _, err := utils.MatchResourceReference(cv, toi.TypeTOIPackage, metav1.NewResourceRef(rid), nil) if err != nil { - return nil, errors.Wrapf(err, "installer resource in %s", common.VersionedElementKey(cv).String()) + return nil, errors.Wrapf(err, "package resource in %s", common.VersionedElementKey(cv).String()) } - var spec PackageSpecification + var spec toi.PackageSpecification err = GetResource(ires, &spec) if err != nil { - return nil, errors.ErrInvalidWrap(err, "installer spec") + return nil, errors.ErrInvalidWrap(err, "package spec") } return ExecuteAction(p, d, name, &spec, creds, params, octx, cv, resolver) } diff --git a/pkg/toi/install/interface.go b/pkg/toi/install/interface.go index 1de9c7764..81bb5c08f 100644 --- a/pkg/toi/install/interface.go +++ b/pkg/toi/install/interface.go @@ -8,6 +8,7 @@ import ( "io" "github.com/open-component-model/ocm/pkg/common/accessio" + "github.com/open-component-model/ocm/pkg/toi" ) const ( @@ -35,7 +36,7 @@ type Operation struct { // ComponentVersion is the name of the root component/version to install ComponentVersion string // Image is the image to invoke - Image Image + 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. diff --git a/pkg/toi/install/spec.go b/pkg/toi/install/spec.go deleted file mode 100644 index 1696ce3c0..000000000 --- a/pkg/toi/install/spec.go +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. -// -// SPDX-License-Identifier: Apache-2.0 - -package install - -import ( - "encoding/json" - - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" -) - -const ( - TypeTOIPackage = "toiPackage" - PackageSpecificationMimeType = "application/vnd.toi.ocm.software.package.v1+yaml" -) - -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"` -} - -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"` -} - -type Image struct { - Ref string `json:"ref"` - Digest string `json:"digest"` -} - -//////////////////////////////////////////////////////////////////////////////// - -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"` -} diff --git a/pkg/toi/logging.go b/pkg/toi/logging.go new file mode 100644 index 000000000..8ed9785d5 --- /dev/null +++ b/pkg/toi/logging.go @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.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 new file mode 100644 index 000000000..21bd186c4 --- /dev/null +++ b/pkg/toi/spec.go @@ -0,0 +1,163 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package toi + +import ( + "encoding/json" + "fmt" + + "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" + "github.com/open-component-model/ocm/pkg/errors" +) + +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]*metav1.ResourceReference `json:"additionalResources,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 index 5ace7afc0..d6d8f3a2b 100644 --- a/pkg/toi/support/README.md +++ b/pkg/toi/support/README.md @@ -1,4 +1,4 @@ -### Suport for TOI Executors +### Support for TOI Executors This package provides a generic command line tool support to provide TOI executor CLIs. diff --git a/pkg/toi/support/app.go b/pkg/toi/support/app.go index f186ccdb2..faa261d2d 100644 --- a/pkg/toi/support/app.go +++ b/pkg/toi/support/app.go @@ -10,24 +10,27 @@ import ( _ "github.com/open-component-model/ocm/pkg/contexts/clictx/config" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/pflag" common2 "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common" "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" 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/errors" + "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 } @@ -60,7 +63,7 @@ func NewCLICommand(ctx ocm.Context, name string, exec func(options *ExecutorOpti return opts.Complete() }, RunE: func(cmd *cobra.Command, args []string) error { - logrus.Infof("This is %s (%s)", name, version.Get().String()) + 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() }, @@ -75,6 +78,7 @@ func NewCLICommand(ctx ocm.Context, name string, exec func(options *ExecutorOpti } 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") @@ -82,12 +86,13 @@ func (o *BootstrapperCLIOptions) AddFlags(fs *pflag.FlagSet) { 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.OCMConfig, "config", "", "", "bootstrapper configuration input file") + 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) } @@ -149,6 +154,7 @@ func (o *BootstrapperCLIOptions) Complete() error { return fmt.Errorf("unable to parse labels: %w", err) } + o.Logger = toi.Log return nil } diff --git a/pkg/toi/support/support.go b/pkg/toi/support/support.go index dd08333c3..8d1306f5c 100644 --- a/pkg/toi/support/support.go +++ b/pkg/toi/support/support.go @@ -8,6 +8,7 @@ import ( "fmt" "io" + "github.com/mandelsoft/logging" "github.com/mandelsoft/vfs/pkg/vfs" "github.com/open-component-model/ocm/pkg/common" @@ -26,6 +27,7 @@ import ( type ExecutorOptions struct { Context ocm.Context + Logger logging.Logger OutputContext out.Context Action string ComponentVersionName string @@ -160,7 +162,7 @@ func (o *ExecutorOptions) Complete() error { 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 crednetial repository") + return errors.Wrapf(err, "cannot get default memory based credential repository") } o.CredentialRepo = c } diff --git a/pkg/utils/extract.go b/pkg/utils/extract.go index 5cb3584ed..2cc08006a 100644 --- a/pkg/utils/extract.go +++ b/pkg/utils/extract.go @@ -10,6 +10,7 @@ import ( "fmt" "io" "os" + "path/filepath" "github.com/mandelsoft/vfs/pkg/vfs" ) @@ -32,6 +33,10 @@ func ExtractTarToFs(fs vfs.FileSystem, in io.Reader) error { return fmt.Errorf("unable to create directory %s: %w", header.Name, err) } case tar.TypeReg: + dir := filepath.Dir(header.Name) + if err := fs.MkdirAll(dir, 0o766); err != nil { + return fmt.Errorf("unable to create directory %s: %w", dir, err) + } file, err := fs.OpenFile(header.Name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, vfs.FileMode(header.Mode)) if err != nil { return fmt.Errorf("unable to open file %s: %w", header.Name, err)