From 21baa840b7dbf47ca4a0c98bdf58a60a6784d43e Mon Sep 17 00:00:00 2001 From: Uwe Krueger Date: Tue, 25 Oct 2022 15:13:13 +0200 Subject: [PATCH 1/6] simple plugin concept Alpha version Using plugins it is possible now to provide additional access methods by separate executables found in a ocm plugin directory. The plugin executable must implement a set of commands: - info The info command must return a JSON object describing the plugin and its capabilities. So far, it might only provide access methods. It is intended to extend this by downloader and uploader implementations. - accessmethod all access relevant capabilities are available as sub commands for the accessmethod command - validate a given access specification is validated. It returns additional derived metadata for the given specification - a human readable info text - the consumer id required to determine optional credentials. - the media type, either derived from the spec or a constant. - a hint usable as reference name for local blobs to reconstruct a useful name for exploded imports. - get -c dump the provided content to stdout - put -c -m -h not yet used, intended for the uploader support It reads the data from stdin and puts a resulting access spec as JSON on stdout The library now provides a command line frame for a plugin implementation, which must implement a plugin and accessmethod interface. The ocm CLI now provides information about configured plugins. The OCM context can be configured to register the capabilities of detected plugins, which make the addutional extensions available for the regular API usage. --- Makefile | 11 +- cmds/demoplugin/accessmethods/demo.go | 91 ++++++++++ cmds/demoplugin/accessmethods/writer.go | 58 +++++++ cmds/demoplugin/main.go | 24 +++ cmds/ocm/app/app.go | 3 +- .../common/handlers/pluginhdlr/typehandler.go | 71 ++++++++ cmds/ocm/commands/ocmcmds/names/names.go | 1 + cmds/ocm/commands/ocmcmds/plugins/cmd.go | 25 +++ cmds/ocm/commands/ocmcmds/plugins/get/cmd.go | 111 ++++++++++++ .../commands/ocmcmds/plugins/get/cmd_test.go | 54 ++++++ .../ocmcmds/plugins/get/suite_test.go | 17 ++ .../ocmcmds/plugins/get/testdata/test | 21 +++ cmds/ocm/commands/verbs/get/cmd.go | 2 + cmds/ocm/pkg/utils/handling.go | 2 +- docs/reference/ocm.md | 4 + docs/reference/ocm_attributes.md | 4 + docs/reference/ocm_get.md | 1 + docs/reference/ocm_get_plugins.md | 90 ++++++++++ pkg/common/accessio/digestwriter.go | 52 ++++++ pkg/contexts/credentials/internal/identity.go | 5 + .../ocm/accessmethods/plugin/method.go | 116 +++++++++++++ .../ocm/accessmethods/plugin/method_test.go | 89 ++++++++++ .../ocm/accessmethods/plugin/plugin.go | 116 +++++++++++++ .../ocm/accessmethods/plugin/suite_test.go | 17 ++ .../ocm/accessmethods/plugin/testdata/test | 39 +++++ pkg/contexts/ocm/accessmethods/plugin/type.go | 35 ++++ pkg/contexts/ocm/attrs/init.go | 1 + .../ocm/attrs/plugincacheattr/attr.go | 31 ++++ pkg/contexts/ocm/attrs/plugindirattr/attr.go | 81 +++++++++ .../ocm/attrs/plugindirattr/attr_test.go | 30 ++++ .../ocm/attrs/plugindirattr/suite_test.go | 17 ++ pkg/contexts/ocm/internal/accesstypes.go | 7 +- pkg/contexts/ocm/internal/context.go | 4 +- pkg/contexts/ocm/plugin/cache/cache.go | 157 +++++++++++++++++ pkg/contexts/ocm/plugin/config/type.go | 54 ++++++ pkg/contexts/ocm/plugin/interface.go | 14 ++ pkg/contexts/ocm/plugin/internal/access.go | 16 ++ .../ocm/plugin/internal/descriptor.go | 24 +++ pkg/contexts/ocm/plugin/plugin.go | 158 ++++++++++++++++++ pkg/contexts/ocm/plugin/plugin_test.go | 63 +++++++ pkg/contexts/ocm/plugin/ppi/accessmethod.go | 13 ++ .../ocm/plugin/ppi/cmds/accessmethod/cmd.go | 29 ++++ .../plugin/ppi/cmds/accessmethod/get/cmd.go | 80 +++++++++ .../plugin/ppi/cmds/accessmethod/put/cmd.go | 104 ++++++++++++ .../ppi/cmds/accessmethod/validate/cmd.go | 77 +++++++++ pkg/contexts/ocm/plugin/ppi/cmds/app.go | 74 ++++++++ pkg/contexts/ocm/plugin/ppi/cmds/info/cmd.go | 32 ++++ pkg/contexts/ocm/plugin/ppi/doc.go | 7 + pkg/contexts/ocm/plugin/ppi/interface.go | 43 +++++ pkg/contexts/ocm/plugin/ppi/options.go | 21 +++ pkg/contexts/ocm/plugin/ppi/plugin.go | 97 +++++++++++ pkg/contexts/ocm/plugin/ppi/utils.go | 38 +++++ pkg/contexts/ocm/plugin/suite_test.go | 17 ++ pkg/contexts/ocm/plugin/testdata/test | 39 +++++ pkg/contexts/ocm/plugin/utils.go | 84 ++++++++++ .../transferhandler/standard/handler_test.go | 5 +- pkg/contexts/ocm/utils/configure.go | 4 +- pkg/errors/error.go | 15 +- pkg/errors/format.go | 7 +- pkg/testutils/utils.go | 2 +- 60 files changed, 2492 insertions(+), 12 deletions(-) create mode 100644 cmds/demoplugin/accessmethods/demo.go create mode 100644 cmds/demoplugin/accessmethods/writer.go create mode 100644 cmds/demoplugin/main.go create mode 100644 cmds/ocm/commands/ocmcmds/common/handlers/pluginhdlr/typehandler.go create mode 100644 cmds/ocm/commands/ocmcmds/plugins/cmd.go create mode 100644 cmds/ocm/commands/ocmcmds/plugins/get/cmd.go create mode 100644 cmds/ocm/commands/ocmcmds/plugins/get/cmd_test.go create mode 100644 cmds/ocm/commands/ocmcmds/plugins/get/suite_test.go create mode 100755 cmds/ocm/commands/ocmcmds/plugins/get/testdata/test create mode 100644 docs/reference/ocm_get_plugins.md create mode 100644 pkg/common/accessio/digestwriter.go create mode 100644 pkg/contexts/ocm/accessmethods/plugin/method.go create mode 100644 pkg/contexts/ocm/accessmethods/plugin/method_test.go create mode 100644 pkg/contexts/ocm/accessmethods/plugin/plugin.go create mode 100644 pkg/contexts/ocm/accessmethods/plugin/suite_test.go create mode 100755 pkg/contexts/ocm/accessmethods/plugin/testdata/test create mode 100644 pkg/contexts/ocm/accessmethods/plugin/type.go create mode 100644 pkg/contexts/ocm/attrs/plugincacheattr/attr.go create mode 100644 pkg/contexts/ocm/attrs/plugindirattr/attr.go create mode 100644 pkg/contexts/ocm/attrs/plugindirattr/attr_test.go create mode 100644 pkg/contexts/ocm/attrs/plugindirattr/suite_test.go create mode 100644 pkg/contexts/ocm/plugin/cache/cache.go create mode 100644 pkg/contexts/ocm/plugin/config/type.go create mode 100644 pkg/contexts/ocm/plugin/interface.go create mode 100644 pkg/contexts/ocm/plugin/internal/access.go create mode 100644 pkg/contexts/ocm/plugin/internal/descriptor.go create mode 100644 pkg/contexts/ocm/plugin/plugin.go create mode 100644 pkg/contexts/ocm/plugin/plugin_test.go create mode 100644 pkg/contexts/ocm/plugin/ppi/accessmethod.go create mode 100644 pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/cmd.go create mode 100644 pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/get/cmd.go create mode 100644 pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/put/cmd.go create mode 100644 pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/validate/cmd.go create mode 100644 pkg/contexts/ocm/plugin/ppi/cmds/app.go create mode 100644 pkg/contexts/ocm/plugin/ppi/cmds/info/cmd.go create mode 100644 pkg/contexts/ocm/plugin/ppi/doc.go create mode 100644 pkg/contexts/ocm/plugin/ppi/interface.go create mode 100644 pkg/contexts/ocm/plugin/ppi/options.go create mode 100644 pkg/contexts/ocm/plugin/ppi/plugin.go create mode 100644 pkg/contexts/ocm/plugin/ppi/utils.go create mode 100644 pkg/contexts/ocm/plugin/suite_test.go create mode 100755 pkg/contexts/ocm/plugin/testdata/test create mode 100644 pkg/contexts/ocm/plugin/utils.go diff --git a/Makefile b/Makefile index dca31270c..3824a4055 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,6 @@ build: ${SOURCES} -X github.com/open-component-model/ocm/pkg/version.buildDate=$(shell date --rfc-3339=seconds | sed 's/ /T/')" \ -o bin/ocm \ ./cmds/ocm - go build -ldflags "-s -w \ -X github.com/open-component-model/ocm/pkg/version.gitVersion=$(EFFECTIVE_VERSION) \ -X github.com/open-component-model/ocm/pkg/version.gitTreeState=$(GIT_TREE_STATE) \ @@ -38,6 +37,14 @@ build: ${SOURCES} -X github.com/open-component-model/ocm/pkg/version.buildDate=$(shell date --rfc-3339=seconds | sed 's/ /T/')" \ -o bin/helminstaller \ ./cmds/helminstaller + go build -ldflags "-s -w \ + -X github.com/open-component-model/ocm/pkg/version.gitVersion=$(EFFECTIVE_VERSION) \ + -X github.com/open-component-model/ocm/pkg/version.gitTreeState=$(GIT_TREE_STATE) \ + -X github.com/open-component-model/ocm/pkg/version.gitCommit=$(COMMIT) \ + -X github.com/open-component-model/ocm/pkg/version.buildDate=$(shell date --rfc-3339=seconds | sed 's/ /T/')" \ + -o bin/demor \ + ./cmds/demoplugin + .PHONY: install-requirements install-requirements: @@ -126,4 +133,4 @@ generate-deepcopy: controller-gen ## Generate code containing DeepCopy, DeepCopy 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 diff --git a/cmds/demoplugin/accessmethods/demo.go b/cmds/demoplugin/accessmethods/demo.go new file mode 100644 index 000000000..f981c9f4c --- /dev/null +++ b/cmds/demoplugin/accessmethods/demo.go @@ -0,0 +1,91 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package accessmethods + +import ( + out "fmt" + "io" + "os" + "strings" + + "github.com/mandelsoft/filepath/pkg/filepath" + "github.com/open-component-model/ocm/pkg/contexts/credentials" + "github.com/open-component-model/ocm/pkg/contexts/oci/identity" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" + "github.com/open-component-model/ocm/pkg/runtime" +) + +const CONSUMER_TYPE = "demo" + +type AccessSpec struct { + runtime.ObjectVersionedType `json:",inline"` + + Path string `json:"path"` + MediaType string `json:"mediaType,omitempty"` +} + +type AccessMethod struct { + ppi.AccessMethodBase +} + +var _ ppi.AccessMethod = (*AccessMethod)(nil) + +func New() ppi.AccessMethod { + return &AccessMethod{ + AccessMethodBase: ppi.MustNewAccessMethodBase("demo", "", &AccessSpec{}), + } +} + +func (a *AccessMethod) Decode(data []byte, unmarshaler runtime.Unmarshaler) (runtime.TypedObject, error) { + if unmarshaler == nil { + unmarshaler = runtime.DefaultYAMLEncoding + } + var spec AccessSpec + err := unmarshaler.Unmarshal(data, &spec) + if err != nil { + return nil, err + } + return &spec, nil +} + +func (a *AccessMethod) ValidateSpecification(p ppi.Plugin, spec ppi.AccessSpec) (*ppi.AccessSpecInfo, error) { + var info ppi.AccessSpecInfo + + my := spec.(*AccessSpec) + + if my.Path == "" { + return nil, out.Errorf("path not specified") + } + if strings.HasPrefix(my.Path, "/") { + return nil, out.Errorf("path must be relative (%s)", my.Path) + } + if my.MediaType == "" { + return nil, out.Errorf("mediaType not specified") + } + info.MediaType = my.MediaType + info.ConsumerId = credentials.ConsumerIdentity{ + identity.ID_TYPE: CONSUMER_TYPE, + identity.ID_HOSTNAME: "localhost", + identity.ID_PATHPREFIX: my.Path, + } + info.Short = "temp file " + my.Path + info.Hint = "temp file " + my.Path + return &info, nil +} + +func (a *AccessMethod) Reader(p ppi.Plugin, spec ppi.AccessSpec, creds credentials.Credentials) (io.ReadCloser, error) { + my := spec.(*AccessSpec) + + return os.Open(filepath.Join(os.TempDir(), my.Path)) +} + +func (a *AccessMethod) Writer(p ppi.Plugin, mediatype string, creds credentials.Credentials) (io.WriteCloser, ppi.AccessSpecProvider, error) { + file, err := os.CreateTemp(os.TempDir(), "demo.*.blob") + if err != nil { + return nil, nil, err + } + writer := NewWriter(file, mediatype, a.Name(), a.Version()) + return writer, writer.Specification, nil +} diff --git a/cmds/demoplugin/accessmethods/writer.go b/cmds/demoplugin/accessmethods/writer.go new file mode 100644 index 000000000..fe46edb04 --- /dev/null +++ b/cmds/demoplugin/accessmethods/writer.go @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package accessmethods + +import ( + "os" + "path/filepath" + + "github.com/open-component-model/ocm/pkg/common" + "github.com/open-component-model/ocm/pkg/common/accessio" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" + "github.com/open-component-model/ocm/pkg/errors" + "github.com/open-component-model/ocm/pkg/runtime" +) + +type writer = accessio.DigestWriter + +type Writer struct { + *writer + file *os.File + name string + version string + media string + spec *AccessSpec +} + +func NewWriter(file *os.File, media, name, version string) *Writer { + return &Writer{ + writer: accessio.NewDefaultDigestWriter(file), + file: file, + name: name, + version: version, + media: media, + } +} + +func (w *Writer) Close() error { + err := w.writer.Close() + if err == nil { + n := filepath.Join(os.TempDir(), common.DigestToFileName(w.writer.Digest())) + err := os.Rename(w.file.Name(), n) + if err != nil { + return errors.Wrapf(err, "cannot rename %q to %q", w.file.Name(), n) + } + w.spec = &AccessSpec{ + ObjectVersionedType: runtime.NewVersionedObjectType(w.name, w.version), + Path: n, + MediaType: w.media, + } + } + return err +} + +func (w *Writer) Specification() ppi.AccessSpec { + return w.spec +} diff --git a/cmds/demoplugin/main.go b/cmds/demoplugin/main.go new file mode 100644 index 000000000..8a6dd37ea --- /dev/null +++ b/cmds/demoplugin/main.go @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "os" + + "github.com/open-component-model/ocm/cmds/demoplugin/accessmethods" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds" + "github.com/open-component-model/ocm/pkg/version" +) + +func main() { + p := ppi.NewPlugin("demo", version.Get().String()) + + p.RegisterAccessMethod(accessmethods.New()) + err := cmds.NewPluginCommand(p).Execute(os.Args[1:]) + if err != nil { + os.Exit(1) + } +} diff --git a/cmds/ocm/app/app.go b/cmds/ocm/app/app.go index e2c25442e..fc5f60935 100644 --- a/cmds/ocm/app/app.go +++ b/cmds/ocm/app/app.go @@ -55,6 +55,7 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/datacontext" "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/vfsattr" datacfg "github.com/open-component-model/ocm/pkg/contexts/datacontext/config/attrs" + "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/plugincacheattr" "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" @@ -316,7 +317,7 @@ func (o *CLIOptions) Complete() error { } err = ctx.ApplyConfig(spec, "cli") } - return err + return plugincacheattr.Get(o.Context.OCMContext()).RegisterExtensions(nil) } func NewVersionCommand(ctx clictx.Context) *cobra.Command { diff --git a/cmds/ocm/commands/ocmcmds/common/handlers/pluginhdlr/typehandler.go b/cmds/ocm/commands/ocmcmds/common/handlers/pluginhdlr/typehandler.go new file mode 100644 index 000000000..95fa112a3 --- /dev/null +++ b/cmds/ocm/commands/ocmcmds/common/handlers/pluginhdlr/typehandler.go @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package pluginhdlr + +import ( + "github.com/open-component-model/ocm/cmds/ocm/pkg/output" + "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" + "github.com/open-component-model/ocm/pkg/contexts/clictx" + "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/plugincacheattr" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" + "github.com/open-component-model/ocm/pkg/errors" +) + +func Elem(e interface{}) plugin.Plugin { + return e.(*Object).Plugin +} + +//////////////////////////////////////////////////////////////////////////////// + +type Object struct { + Plugin plugin.Plugin +} + +type Manifest struct { + Element *plugin.Descriptor `json:"element"` +} + +func (o *Object) AsManifest() interface{} { + return &Manifest{ + o.Plugin.GetDescriptor(), + } +} + +//////////////////////////////////////////////////////////////////////////////// + +type TypeHandler struct { + octx clictx.OCM +} + +func NewTypeHandler(octx clictx.OCM) utils.TypeHandler { + return &TypeHandler{ + octx: octx, + } +} + +func (h *TypeHandler) Close() error { + return nil +} + +func (h *TypeHandler) All() ([]output.Object, error) { + cache := plugincacheattr.Get(h.octx.Context()) + result := []output.Object{} + + for _, n := range cache.PluginNames() { + result = append(result, &Object{cache.GetPlugin(n)}) + } + return result, nil +} + +func (h *TypeHandler) Get(elemspec utils.ElemSpec) ([]output.Object, error) { + cache := plugincacheattr.Get(h.octx.Context()) + + p := cache.GetPlugin(elemspec.String()) + if p == nil { + return nil, errors.ErrNotFound(ppi.KIND_PLUGIN, elemspec.String()) + } + return []output.Object{p}, nil +} diff --git a/cmds/ocm/commands/ocmcmds/names/names.go b/cmds/ocm/commands/ocmcmds/names/names.go index fd897f53b..e9c155505 100644 --- a/cmds/ocm/commands/ocmcmds/names/names.go +++ b/cmds/ocm/commands/ocmcmds/names/names.go @@ -14,4 +14,5 @@ var ( Sources = []string{"sources", "source", "src", "s"} References = []string{"references", "reference", "refs"} Versions = []string{"versions", "vers", "v"} + Plugins = []string{"plugins", "p"} ) diff --git a/cmds/ocm/commands/ocmcmds/plugins/cmd.go b/cmds/ocm/commands/ocmcmds/plugins/cmd.go new file mode 100644 index 000000000..26abc813c --- /dev/null +++ b/cmds/ocm/commands/ocmcmds/plugins/cmd.go @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package plugins + +import ( + "github.com/spf13/cobra" + + "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" + "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/plugins/get" + "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" + "github.com/open-component-model/ocm/pkg/contexts/clictx" +) + +var Names = names.Plugins + +// NewCommand creates a new command. +func NewCommand(ctx clictx.Context) *cobra.Command { + cmd := utils.MassageCommand(&cobra.Command{ + Short: "Commands related to OCM plugins", + }, Names...) + cmd.AddCommand(get.NewCommand(ctx, get.Verb)) + return cmd +} diff --git a/cmds/ocm/commands/ocmcmds/plugins/get/cmd.go b/cmds/ocm/commands/ocmcmds/plugins/get/cmd.go new file mode 100644 index 000000000..0f1d54161 --- /dev/null +++ b/cmds/ocm/commands/ocmcmds/plugins/get/cmd.go @@ -0,0 +1,111 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package get + +import ( + "strings" + + "github.com/spf13/cobra" + + handler "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/handlers/pluginhdlr" + "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" + "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" + "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" + "github.com/open-component-model/ocm/cmds/ocm/pkg/output" + "github.com/open-component-model/ocm/cmds/ocm/pkg/processing" + "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" + "github.com/open-component-model/ocm/pkg/contexts/clictx" +) + +var ( + Names = names.Plugins + Verb = verbs.Get +) + +type Command struct { + utils.BaseCommand + + Names []string +} + +// NewCommand creates a new ctf command. +func NewCommand(ctx clictx.Context, names ...string) *cobra.Command { + return utils.SetupCommand( + &Command{ + BaseCommand: utils.NewBaseCommand(ctx, repooption.New(), output.OutputOptions(outputs)), + }, + utils.Names(Names, names...)..., + ) +} + +func (o *Command) ForName(name string) *cobra.Command { + return &cobra.Command{ + Use: "[] {}", + Short: "get plugins", + Long: ` +Get lists information for all plugins specified, if no plugin is specified +all registered ones are listed. +`, + Example: ` +$ ocm get plugins +$ ocm get plugins demo -o yaml +`, + } +} + +func (o *Command) Complete(args []string) error { + o.Names = args + return nil +} + +func (o *Command) Run() error { + hdlr := handler.NewTypeHandler(o.Context.OCM()) + return utils.HandleArgs(output.From(o), hdlr, o.Names...) +} + +///////////////////////////////////////////////////////////////////////////// + +func TableOutput(opts *output.Options, mapping processing.MappingFunction, wide ...string) *output.TableOutput { + def := &output.TableOutput{ + Headers: output.Fields("PLUGIN", "VERSION", "DESCRIPTION", wide), + Options: opts, + Mapping: mapping, + } + return def +} + +///////////////////////////////////////////////////////////////////////////// + +var outputs = output.NewOutputs(getRegular, output.Outputs{ + "wide": getWide, +}).AddManifestOutputs() + +func getRegular(opts *output.Options) output.Output { + return TableOutput(opts, mapGetRegularOutput).New() +} + +func getWide(opts *output.Options) output.Output { + return TableOutput(opts, mapGetWideOutput, "ACCESSMETHODS").New() +} + +func mapGetRegularOutput(e interface{}) interface{} { + p := handler.Elem(e) + return []string{p.Name(), p.Version(), p.Message()} +} + +func mapGetWideOutput(e interface{}) interface{} { + p := handler.Elem(e) + d := p.GetDescriptor() + + var list []string + for _, m := range d.AccessMethods { + n := m.Name + if m.Version != "" { + n += "/" + m.Version + } + list = append(list, n) + } + return output.Fields(mapGetRegularOutput(e), strings.Join(list, ", ")) +} diff --git a/cmds/ocm/commands/ocmcmds/plugins/get/cmd_test.go b/cmds/ocm/commands/ocmcmds/plugins/get/cmd_test.go new file mode 100644 index 000000000..c9a51cf3b --- /dev/null +++ b/cmds/ocm/commands/ocmcmds/plugins/get/cmd_test.go @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package get_test + +import ( + "bytes" + "path/filepath" + + . "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" +) + +const PLUGINS = "/testdata" + +var _ = Describe("Test Environment", func() { + var env *TestEnv + var path string + + BeforeEach(func() { + env = NewTestEnv(TestData()) + + p, err := filepath.Abs("testdata") + Expect(err).To(Succeed()) + path = p + }) + + AfterEach(func() { + env.Cleanup() + }) + + It("get plugins", func() { + buf := bytes.NewBuffer(nil) + Expect(env.CatchOutput(buf).Execute("-X", "plugindir="+path, "get", "plugins")).To(Succeed()) + Expect(buf.String()).To(StringEqualTrimmedWithContext( + ` +PLUGIN VERSION DESCRIPTION +test v1 a test plugin without function +`)) + }) + It("get plugins with additional info", func() { + buf := bytes.NewBuffer(nil) + Expect(env.CatchOutput(buf).Execute("-X", "plugindir="+path, "get", "plugins", "-o", "wide")).To(Succeed()) + Expect(buf.String()).To(StringEqualTrimmedWithContext( + ` +PLUGIN VERSION DESCRIPTION ACCESSMETHODS +test v1 a test plugin without function test, test/v1 +`)) + }) + +}) diff --git a/cmds/ocm/commands/ocmcmds/plugins/get/suite_test.go b/cmds/ocm/commands/ocmcmds/plugins/get/suite_test.go new file mode 100644 index 000000000..0986c50bf --- /dev/null +++ b/cmds/ocm/commands/ocmcmds/plugins/get/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 get_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestConfig(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "OCM get components") +} diff --git a/cmds/ocm/commands/ocmcmds/plugins/get/testdata/test b/cmds/ocm/commands/ocmcmds/plugins/get/testdata/test new file mode 100755 index 000000000..265973086 --- /dev/null +++ b/cmds/ocm/commands/ocmcmds/plugins/get/testdata/test @@ -0,0 +1,21 @@ +#!/bin/bash + +# SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +# +# SPDX-License-Identifier: Apache-2.0 + +NAME="$(basename "$0")" + +Error() { + echo '{ "error": "'$1'" }' >&2 + exit 1 +} + +Info() { + echo '{"version":"v1","pluginName":"'$NAME'","pluginVersion":"v1","shortDescription":"a test plugin without function","description":"a test plugin with access method test","accessMethods":[{"name":"test","shortDescription":"test access","description":""},{"name":"test","version":"v1","shortDescription":"test access","description":""}]}' +} + +case "$1" in + info) Info;; + *) Error "invalid command $1";; +esac diff --git a/cmds/ocm/commands/verbs/get/cmd.go b/cmds/ocm/commands/verbs/get/cmd.go index 3b26d2ca2..fc955d644 100644 --- a/cmds/ocm/commands/verbs/get/cmd.go +++ b/cmds/ocm/commands/verbs/get/cmd.go @@ -10,6 +10,7 @@ import ( credentials "github.com/open-component-model/ocm/cmds/ocm/commands/misccmds/credentials/get" artefacts "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/artefacts/get" components "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/components/get" + plugins "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/plugins/get" references "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/references/get" resources "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/resources/get" sources "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/sources/get" @@ -29,5 +30,6 @@ func NewCommand(ctx clictx.Context) *cobra.Command { cmd.AddCommand(references.NewCommand(ctx)) cmd.AddCommand(sources.NewCommand(ctx)) cmd.AddCommand(credentials.NewCommand(ctx)) + cmd.AddCommand(plugins.NewCommand(ctx)) return cmd } diff --git a/cmds/ocm/pkg/utils/handling.go b/cmds/ocm/pkg/utils/handling.go index 7e821cdbb..e9c9cc6c1 100644 --- a/cmds/ocm/pkg/utils/handling.go +++ b/cmds/ocm/pkg/utils/handling.go @@ -74,7 +74,7 @@ func HandleOutput(output output.Output, handler TypeHandler, specs ...ElemSpec) return err } if result == nil { - return fmt.Errorf("all mode not support") + return fmt.Errorf("all mode not supported") } for _, r := range result { err := output.Add(r) diff --git a/docs/reference/ocm.md b/docs/reference/ocm.md index 6e8dc17ac..bb927353e 100644 --- a/docs/reference/ocm.md +++ b/docs/reference/ocm.md @@ -93,6 +93,10 @@ attributes are supported: Upload local OCI artefact blobs to a dedicated repository. +- github.com/mandelsoft/ocm/plugindir [plugindir]: *plugin directory* + + Directory to look for OCM plugin executables. + - github.com/mandelsoft/ocm/signing: *JSON* Public and private Key settings given as JSON document with the following diff --git a/docs/reference/ocm_attributes.md b/docs/reference/ocm_attributes.md index 097a03303..b08f552fd 100644 --- a/docs/reference/ocm_attributes.md +++ b/docs/reference/ocm_attributes.md @@ -30,6 +30,10 @@ OCM library: Upload local OCI artefact blobs to a dedicated repository. +- github.com/mandelsoft/ocm/plugindir [plugindir]: *plugin directory* + + Directory to look for OCM plugin executables. + - github.com/mandelsoft/ocm/signing: *JSON* Public and private Key settings given as JSON document with the following diff --git a/docs/reference/ocm_get.md b/docs/reference/ocm_get.md index 6cc5a946f..e937c0238 100644 --- a/docs/reference/ocm_get.md +++ b/docs/reference/ocm_get.md @@ -24,6 +24,7 @@ ocm get [] ... * [ocm get artefacts](ocm_get_artefacts.md) — get artefact version * [ocm get componentversions](ocm_get_componentversions.md) — get component version * [ocm get credentials](ocm_get_credentials.md) — Get credentials for a dedicated consumer spec +* [ocm get plugins](ocm_get_plugins.md) — get plugins * [ocm get references](ocm_get_references.md) — get references of a component version * [ocm get resources](ocm_get_resources.md) — get resources of a component version * [ocm get sources](ocm_get_sources.md) — get sources of a component version diff --git a/docs/reference/ocm_get_plugins.md b/docs/reference/ocm_get_plugins.md new file mode 100644 index 000000000..64f9c9fc7 --- /dev/null +++ b/docs/reference/ocm_get_plugins.md @@ -0,0 +1,90 @@ +## ocm get plugins — Get Plugins + +### Synopsis + +``` +ocm get plugins [] {} +``` + +### Options + +``` + -h, --help help for plugins + -o, --output string output mode (JSON, json, wide, yaml) + --repo string repository name or spec + -s, --sort stringArray sort fields +``` + +### Description + + +Get lists information for all plugins specified, if no plugin is specified +all registered ones are listed. + +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): +- `ArtefactSet` +- `CommonTransportFormat` +- `DockerDaemon` +- `Empty` +- `OCIRegistry` +- `oci` +- `ociRegistry` + +With the option --output the output mode can be selected. +The following modes are supported: + - JSON + - json + - wide + - yaml + + +### Examples + +``` +$ ocm get plugins +$ ocm get plugins demo -o yaml +``` + +### SEE ALSO + +##### Parents + +* [ocm get](ocm_get.md) — Get information about artefacts and components +* [ocm](ocm.md) — Open Component Model command line client + diff --git a/pkg/common/accessio/digestwriter.go b/pkg/common/accessio/digestwriter.go new file mode 100644 index 000000000..0759cd147 --- /dev/null +++ b/pkg/common/accessio/digestwriter.go @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package accessio + +import ( + "io" + + "github.com/opencontainers/go-digest" +) + +type writer io.WriteCloser + +type DigestWriter struct { + writer + digester digest.Digester + count int64 +} + +func (r *DigestWriter) Size() int64 { + return r.count +} + +func (r *DigestWriter) Digest() digest.Digest { + return r.digester.Digest() +} + +func (r *DigestWriter) Write(buf []byte) (int, error) { + c, err := r.writer.Write(buf) + if c > 0 { + r.count += int64(c) + r.digester.Hash().Write(buf[:c]) + } + return c, err +} + +func NewDefaultDigestWriter(w io.WriteCloser) *DigestWriter { + return &DigestWriter{ + writer: w, + digester: digest.Canonical.Digester(), + count: 0, + } +} + +func NewDigestWriterWith(algorithm digest.Algorithm, w io.WriteCloser) *DigestWriter { + return &DigestWriter{ + writer: w, + digester: algorithm.Digester(), + count: 0, + } +} diff --git a/pkg/contexts/credentials/internal/identity.go b/pkg/contexts/credentials/internal/identity.go index 6e9143569..e8c31f2bf 100644 --- a/pkg/contexts/credentials/internal/identity.go +++ b/pkg/contexts/credentials/internal/identity.go @@ -102,6 +102,11 @@ func IdentityByURL(url string) ConsumerIdentity { return ConsumerIdentity{"url": url} } +// Type returns the required consumer type. +func (i ConsumerIdentity) Type() string { + return i[ID_TYPE] +} + // String returns the string representation of an identity. func (i ConsumerIdentity) String() string { data, err := json.Marshal(i) diff --git a/pkg/contexts/ocm/accessmethods/plugin/method.go b/pkg/contexts/ocm/accessmethods/plugin/method.go new file mode 100644 index 000000000..d2a2dd2bc --- /dev/null +++ b/pkg/contexts/ocm/accessmethods/plugin/method.go @@ -0,0 +1,116 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package plugin + +import ( + "io" + "sync" + + "github.com/open-component-model/ocm/pkg/common/accessio" + "github.com/open-component-model/ocm/pkg/common/accessobj" + "github.com/open-component-model/ocm/pkg/contexts/credentials" + "github.com/open-component-model/ocm/pkg/contexts/ocm" + "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" + "github.com/open-component-model/ocm/pkg/runtime" +) + +type AccessSpec struct { + runtime.UnstructuredVersionedTypedObject `json:",inline"` + handler *PluginHandler +} + +var ( + _ cpi.AccessSpec = &AccessSpec{} + _ cpi.HintProvider = &AccessSpec{} +) + +func (s *AccessSpec) AccessMethod(cv cpi.ComponentVersionAccess) (cpi.AccessMethod, error) { + return s.handler.AccessMethod(s, cv) +} + +func (s *AccessSpec) Describe(ctx cpi.Context) string { + return s.handler.Describe(s, ctx) +} + +func (_ *AccessSpec) IsLocal(cpi.Context) bool { + return false +} + +func (s *AccessSpec) GetMimeType() string { + return s.handler.GetMimeType(s) +} + +func (s *AccessSpec) GetReferenceHint(cv cpi.ComponentVersionAccess) string { + return s.handler.GetReferenceHint(s, cv) +} + +func (s *AccessSpec) Handler() *PluginHandler { + return s.handler +} + +//////////////////////////////////////////////////////////////////////////////// + +type accessMethod struct { + lock sync.Mutex + blob accessio.BlobAccess + ctx ocm.Context + + handler *PluginHandler + spec *AccessSpec + info *ppi.AccessSpecInfo + creds credentials.Credentials +} + +var _ cpi.AccessMethod = (*accessMethod)(nil) + +func newMethod(p *PluginHandler, spec *AccessSpec, ctx ocm.Context, info *ppi.AccessSpecInfo, creds credentials.Credentials) *accessMethod { + return &accessMethod{ + ctx: ctx, + handler: p, + spec: spec, + info: info, + creds: creds, + } +} + +func (m *accessMethod) GetKind() string { + return m.spec.GetKind() +} + +func (m *accessMethod) Close() error { + m.lock.Lock() + defer m.lock.Unlock() + if m.blob != nil { + m.blob.Close() + m.blob = nil + } + return nil +} + +func (m *accessMethod) Get() ([]byte, error) { + return accessio.BlobData(m.getBlob()) +} + +func (m *accessMethod) Reader() (io.ReadCloser, error) { + return accessio.BlobReader(m.getBlob()) +} + +func (m *accessMethod) MimeType() string { + return m.info.MediaType +} + +func (m *accessMethod) getBlob() (cpi.BlobAccess, error) { + m.lock.Lock() + defer m.lock.Unlock() + + if m.blob != nil { + return m.blob, nil + } + + m.blob = accessobj.CachedBlobAccessForWriter(m.ctx, m.MimeType(), plugin.NewAccessDataWriter(m.handler.plug, m.spec.GetType())) + return m.blob, nil +} diff --git a/pkg/contexts/ocm/accessmethods/plugin/method_test.go b/pkg/contexts/ocm/accessmethods/plugin/method_test.go new file mode 100644 index 000000000..a5bf88128 --- /dev/null +++ b/pkg/contexts/ocm/accessmethods/plugin/method_test.go @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package plugin_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/open-component-model/ocm/pkg/env/builder" + . "github.com/open-component-model/ocm/pkg/testutils" + + "github.com/open-component-model/ocm/pkg/common/accessio" + "github.com/open-component-model/ocm/pkg/common/accessobj" + "github.com/open-component-model/ocm/pkg/contexts/ocm" + "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/plugincacheattr" + "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/plugindirattr" + metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/cache" + "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" +) + +const ARCH = "ctf" +const COMP = "github.com/mandelsoft/comp" +const VERS = "1.0.0" +const PROVIDER = "mandelsoft" + +var _ = Describe("setup plugin cache", func() { + var ctx ocm.Context + var registry cache.Cache + var env *Builder + + var accessSpec ocm.AccessSpec + + BeforeEach(func() { + var err error + + accessSpec, err = ocm.NewGenericAccessSpec(` +type: test +someattr: value +`) + Expect(err).To(Succeed()) + + env = NewBuilder(nil) + ctx = env.OCMContext() + plugindirattr.Set(ctx, "testdata") + registry = plugincacheattr.Get(ctx) + Expect(registry.RegisterExtensions(ctx)).To(Succeed()) + p := registry.GetPlugin("test") + Expect(p).NotTo(BeNil()) + }) + + AfterEach(func() { + env.Cleanup() + }) + + It("registers access methods", func() { + + env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { + env.Component(COMP, func() { + env.Version(VERS, func() { + env.Provider(PROVIDER) + env.Resource("testdata", VERS, "PlainText", metav1.ExternalRelation, func() { + env.Access(accessSpec) + }) + }) + }) + }) + + repo, err := ctf.Open(ctx, accessobj.ACC_READONLY, ARCH, 0, env) + Expect(err).To(Succeed()) + defer Close(repo) + + cv, err := repo.LookupComponentVersion(COMP, VERS) + Expect(err).To(Succeed()) + defer Close(cv) + + r, err := cv.GetResourceByIndex(0) + Expect(err).To(Succeed()) + + m, err := r.AccessMethod() + Expect(err).To(Succeed()) + Expect(m.MimeType()).To(Equal("plain/text")) + + data, err := m.Get() + Expect(err).To(Succeed()) + Expect(string(data)).To(Equal("test content\n")) + }) +}) diff --git a/pkg/contexts/ocm/accessmethods/plugin/plugin.go b/pkg/contexts/ocm/accessmethods/plugin/plugin.go new file mode 100644 index 000000000..4d6f8cc73 --- /dev/null +++ b/pkg/contexts/ocm/accessmethods/plugin/plugin.go @@ -0,0 +1,116 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package plugin + +import ( + "bytes" + + "github.com/open-component-model/ocm/pkg/contexts/credentials" + "github.com/open-component-model/ocm/pkg/contexts/credentials/identity/hostpath" + "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" + "github.com/open-component-model/ocm/pkg/errors" +) + +type plug = plugin.Plugin + +type PluginHandler struct { + plug + + // cached info + info *ppi.AccessSpecInfo + err error + orig []byte +} + +func NewPluginHandler(p plugin.Plugin) *PluginHandler { + return &PluginHandler{plug: p} +} + +func (p *PluginHandler) Info(spec *AccessSpec) (*ppi.AccessSpecInfo, error) { + if p.info != nil || p.err != nil { + raw, err := spec.UnstructuredVersionedTypedObject.GetRaw() + if err != nil { + return nil, errors.Wrapf(err, "cannot marshal access specification") + } + if bytes.Equal(raw, p.orig) { + return p.info, p.err + } + } + p.info, p.err = p.Validate(spec) + return p.info, p.err +} + +func (p *PluginHandler) AccessMethod(spec *AccessSpec, cv cpi.ComponentVersionAccess) (cpi.AccessMethod, error) { + mspec := p.GetAccessMethodDescriptor(spec.GetKind(), spec.GetVersion()) + if mspec == nil { + return nil, errors.ErrNotFound(errors.KIND_ACCESSMETHOD, spec.GetType(), ppi.KIND_PLUGIN, p.Name()) + } + info, err := p.Info(spec) + if err != nil { + return nil, err + } + var creds credentials.Credentials + + if len(info.ConsumerId) > 0 { + src, err := cv.GetContext().CredentialsContext().GetCredentialsForConsumer(info.ConsumerId, hostpath.IdentityMatcher(info.ConsumerId.Type())) + if err != nil { + if !errors.IsErrUnknown(err) { + return nil, errors.Wrapf(err, "lookup credentials failed for %s", info.ConsumerId) + } + } else { + creds, err = src.Credentials(cv.GetContext().CredentialsContext()) + if err != nil { + return nil, errors.Wrapf(err, "lookup credentials failed for %s", info.ConsumerId) + } + } + } + return newMethod(p, spec, cv.GetContext(), info, creds), nil +} + +func (p *PluginHandler) Describe(spec *AccessSpec, ctx cpi.Context) string { + mspec := p.GetAccessMethodDescriptor(spec.GetKind(), spec.GetVersion()) + if mspec == nil { + return "unknown type " + spec.GetType() + } + info, err := p.Info(spec) + if err != nil { + return err.Error() + } + return info.Short +} + +func (p *PluginHandler) GetMimeType(spec *AccessSpec) string { + mspec := p.GetAccessMethodDescriptor(spec.GetKind(), spec.GetVersion()) + if mspec == nil { + return "unknown type " + spec.GetType() + } + info, err := p.Info(spec) + if err != nil { + return "" + } + return info.Short +} + +func (p *PluginHandler) GetReferenceHint(spec *AccessSpec, cv cpi.ComponentVersionAccess) string { + mspec := p.GetAccessMethodDescriptor(spec.GetKind(), spec.GetVersion()) + if mspec == nil { + return "unknown type " + spec.GetType() + } + info, err := p.Info(spec) + if err != nil { + return "" + } + return info.Hint +} + +func (p *PluginHandler) Validate(spec *AccessSpec) (*ppi.AccessSpecInfo, error) { + data, err := spec.GetRaw() + if err != nil { + return nil, err + } + return p.plug.Validate(data) +} diff --git a/pkg/contexts/ocm/accessmethods/plugin/suite_test.go b/pkg/contexts/ocm/accessmethods/plugin/suite_test.go new file mode 100644 index 000000000..d9fed29da --- /dev/null +++ b/pkg/contexts/ocm/accessmethods/plugin/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 plugin_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestConfig(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Plugin Access Method Test Suite") +} diff --git a/pkg/contexts/ocm/accessmethods/plugin/testdata/test b/pkg/contexts/ocm/accessmethods/plugin/testdata/test new file mode 100755 index 000000000..2f997edaa --- /dev/null +++ b/pkg/contexts/ocm/accessmethods/plugin/testdata/test @@ -0,0 +1,39 @@ +#!/bin/bash + +# SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +# +# SPDX-License-Identifier: Apache-2.0 + +NAME="$(basename "$0")" + +Error() { + echo '{ "error": "'$1'" }' >&2 + exit 1 +} + +Info() { + echo '{"version":"v1","pluginName":"'$NAME'","pluginVersion":"v1","shortDescription":"a test plugin","description":"a test plugin with access method test","accessMethods":[{"name":"test","shortDescription":"test access","description":""},{"name":"test","version":"v1","shortDescription":"test access","description":""}]} +' +} + +Get() { + echo "test content" +} + +Validate() { + echo '{"short":"a test","mediaType":"plain/text","description":"","hint":"testfile","consumerId":{"hostname":"localhost","type":"test"}}' +} + +AccessMethod() { + case "$1" in + get) Get "${@:2}";; + validate) Validate "${@:2}";; + *) Error "invalid accessmethod command $1";; + esac +} + +case "$1" in + info) Info;; + accessmethod) AccessMethod "${@:2}";; + *) Error "invalid command $1";; +esac diff --git a/pkg/contexts/ocm/accessmethods/plugin/type.go b/pkg/contexts/ocm/accessmethods/plugin/type.go new file mode 100644 index 000000000..5c1284e56 --- /dev/null +++ b/pkg/contexts/ocm/accessmethods/plugin/type.go @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package plugin + +import ( + "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin" + "github.com/open-component-model/ocm/pkg/runtime" +) + +type accessType struct { + cpi.AccessType + plug plugin.Plugin +} + +var _ cpi.AccessType = (*accessType)(nil) + +func NewType(name string, desc string, p plugin.Plugin) cpi.AccessType { + t := &accessType{ + AccessType: cpi.NewAccessSpecType(name, &AccessSpec{}, desc), + plug: p, + } + return t +} + +func (t *accessType) Decode(data []byte, unmarshaler runtime.Unmarshaler) (runtime.TypedObject, error) { + spec, err := t.AccessType.Decode(data, unmarshaler) + if err != nil { + return nil, err + } + spec.(*AccessSpec).handler = NewPluginHandler(t.plug) + return spec, nil +} diff --git a/pkg/contexts/ocm/attrs/init.go b/pkg/contexts/ocm/attrs/init.go index e35c0653b..bfce6a9eb 100644 --- a/pkg/contexts/ocm/attrs/init.go +++ b/pkg/contexts/ocm/attrs/init.go @@ -8,5 +8,6 @@ import ( _ "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/compatattr" _ "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/keepblobattr" _ "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/ociuploadattr" + _ "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/plugindirattr" _ "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/signingattr" ) diff --git a/pkg/contexts/ocm/attrs/plugincacheattr/attr.go b/pkg/contexts/ocm/attrs/plugincacheattr/attr.go new file mode 100644 index 000000000..0280d8760 --- /dev/null +++ b/pkg/contexts/ocm/attrs/plugincacheattr/attr.go @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package plugincacheattr + +import ( + "github.com/open-component-model/ocm/pkg/contexts/datacontext" + "github.com/open-component-model/ocm/pkg/contexts/ocm" + "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/plugindirattr" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/cache" +) + +const ( + ATTR_KEY = "github.com/mandelsoft/ocm/plugins" +) + +//////////////////////////////////////////////////////////////////////////////// + +func Get(ctx ocm.Context) cache.Cache { + path := plugindirattr.Get(ctx) + + // avoid dead lock reading attribute during attribute creation + return ctx.GetAttributes().GetOrCreateAttribute(ATTR_KEY, func(ctx datacontext.Context) interface{} { + return cache.New(ctx.(ocm.Context), path) + }).(cache.Cache) +} + +func Set(ctx ocm.Context, cache cache.Cache) error { + return ctx.GetAttributes().SetAttribute(ATTR_KEY, cache) +} diff --git a/pkg/contexts/ocm/attrs/plugindirattr/attr.go b/pkg/contexts/ocm/attrs/plugindirattr/attr.go new file mode 100644 index 000000000..faab56d98 --- /dev/null +++ b/pkg/contexts/ocm/attrs/plugindirattr/attr.go @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package plugindirattr + +import ( + "fmt" + "os" + + "github.com/mandelsoft/filepath/pkg/filepath" + "github.com/mandelsoft/vfs/pkg/osfs" + "github.com/mandelsoft/vfs/pkg/vfs" + "github.com/modern-go/reflect2" + + "github.com/open-component-model/ocm/pkg/contexts/datacontext" + "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" + "github.com/open-component-model/ocm/pkg/runtime" +) + +const ( + ATTR_KEY = "github.com/mandelsoft/ocm/plugindir" + ATTR_SHORT = "plugindir" + + DEFAULT_PLUGIN_DIR = utils.DEFAULT_OCM_CONFIG_DIR + "/plugins" +) + +func init() { + datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}, ATTR_SHORT) +} + +func DefaultDir(fs vfs.FileSystem) string { + home := os.Getenv("HOME") + if home != "" { + dir := filepath.Join(home, DEFAULT_PLUGIN_DIR) + if ok, err := vfs.DirExists(fs, dir); ok && err == nil { + return dir + } + } + return "" +} + +type AttributeType struct{} + +func (a AttributeType) Name() string { + return ATTR_KEY +} + +func (a AttributeType) Description() string { + return ` +*plugin directory* +Directory to look for OCM plugin executables. +` +} + +func (a AttributeType) Encode(v interface{}, marshaller runtime.Marshaler) ([]byte, error) { + if _, ok := v.(string); !ok { + return nil, fmt.Errorf("directory path required") + } + return marshaller.Marshal(v) +} + +func (a AttributeType) Decode(data []byte, unmarshaller runtime.Unmarshaler) (interface{}, error) { + var value string + err := unmarshaller.Unmarshal(data, &value) + return value, err +} + +//////////////////////////////////////////////////////////////////////////////// + +func Get(ctx datacontext.Context) string { + a := ctx.GetAttributes().GetAttribute(ATTR_KEY) + if reflect2.IsNil(a) { + return DefaultDir(osfs.New()) + } + return a.(string) +} + +func Set(ctx datacontext.Context, path string) error { + return ctx.GetAttributes().SetAttribute(ATTR_KEY, path) +} diff --git a/pkg/contexts/ocm/attrs/plugindirattr/attr_test.go b/pkg/contexts/ocm/attrs/plugindirattr/attr_test.go new file mode 100644 index 000000000..b2db4e499 --- /dev/null +++ b/pkg/contexts/ocm/attrs/plugindirattr/attr_test.go @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package plugindirattr_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/open-component-model/ocm/pkg/contexts/config" + "github.com/open-component-model/ocm/pkg/contexts/datacontext" + me "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/plugindirattr" +) + +var _ = Describe("attribute", func() { + var ctx config.Context + + attr := "___test___" + + BeforeEach(func() { + ctx = config.WithSharedAttributes(datacontext.New(nil)).New() + }) + + It("local setting", func() { + Expect(me.Get(ctx)).NotTo(Equal(attr)) + Expect(me.Set(ctx, attr)).To(Succeed()) + Expect(me.Get(ctx)).To(BeIdenticalTo(attr)) + }) +}) diff --git a/pkg/contexts/ocm/attrs/plugindirattr/suite_test.go b/pkg/contexts/ocm/attrs/plugindirattr/suite_test.go new file mode 100644 index 000000000..9fc799c63 --- /dev/null +++ b/pkg/contexts/ocm/attrs/plugindirattr/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 plugindirattr_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestConfig(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Plugin Directory Attribute") +} diff --git a/pkg/contexts/ocm/internal/accesstypes.go b/pkg/contexts/ocm/internal/accesstypes.go index 2262492e7..6b6680b55 100644 --- a/pkg/contexts/ocm/internal/accesstypes.go +++ b/pkg/contexts/ocm/internal/accesstypes.go @@ -188,6 +188,11 @@ var _ AccessSpec = &UnknownAccessSpec{} //////////////////////////////////////////////////////////////////////////////// +type EvaluatableAccessSpec interface { + AccessSpec + Evaluate(ctx Context) (AccessSpec, error) +} + type GenericAccessSpec struct { runtime.UnstructuredVersionedTypedObject `json:",inline"` } @@ -204,7 +209,7 @@ func NewGenericAccessSpec(spec string) (*GenericAccessSpec, error) { func (s *GenericAccessSpec) Describe(ctx Context) string { eff, err := s.Evaluate(ctx) if err != nil { - return fmt.Sprintf("invalid access specificatio: %s", err.Error()) + return fmt.Sprintf("invalid access specification: %s", err.Error()) } return eff.Describe(ctx) } diff --git a/pkg/contexts/ocm/internal/context.go b/pkg/contexts/ocm/internal/context.go index ae725db31..022f1563b 100644 --- a/pkg/contexts/ocm/internal/context.go +++ b/pkg/contexts/ocm/internal/context.go @@ -177,12 +177,14 @@ func (c *_context) AccessSpecForConfig(data []byte, unmarshaler runtime.Unmarsha return c.knownAccessTypes.DecodeAccessSpec(data, unmarshaler) } +// AccessSpecForSpec takes an anonymous access specification and tries to map +// it to an effective implementation. func (c *_context) AccessSpecForSpec(spec compdesc.AccessSpec) (AccessSpec, error) { if spec == nil { return nil, nil } if n, ok := spec.(AccessSpec); ok { - if g, ok := spec.(*GenericAccessSpec); ok { + if g, ok := spec.(EvaluatableAccessSpec); ok { return g.Evaluate(c) } return n, nil diff --git a/pkg/contexts/ocm/plugin/cache/cache.go b/pkg/contexts/ocm/plugin/cache/cache.go new file mode 100644 index 000000000..ad91e8d87 --- /dev/null +++ b/pkg/contexts/ocm/plugin/cache/cache.go @@ -0,0 +1,157 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package cache + +import ( + "encoding/json" + "fmt" + "path/filepath" + "sync" + + "github.com/mandelsoft/logging" + "github.com/mandelsoft/vfs/pkg/osfs" + "github.com/mandelsoft/vfs/pkg/vfs" + + cfgcpi "github.com/open-component-model/ocm/pkg/contexts/config/cpi" + "github.com/open-component-model/ocm/pkg/contexts/ocm" + access "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/plugin" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/config" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/internal" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/info" + "github.com/open-component-model/ocm/pkg/errors" + "github.com/open-component-model/ocm/pkg/runtime" + "github.com/open-component-model/ocm/pkg/utils" +) + +var PKG = logging.Package() + +type Cache = *cacheImpl + +var _ config.Target = (*cacheImpl)(nil) + +type cacheImpl struct { + lock sync.RWMutex + + updater cfgcpi.Updater + ctx ocm.Context + plugins map[string]plugin.Plugin + configs map[string]json.RawMessage +} + +func New(ctx ocm.Context, path string) Cache { + c := &cacheImpl{ + ctx: ctx, + plugins: map[string]plugin.Plugin{}, + configs: map[string]json.RawMessage{}, + } + c.updater = cfgcpi.NewUpdater(ctx.ConfigContext(), c) + c.Update() + if path != "" { + c.scan(path) + } + return c +} + +func (c *cacheImpl) Update() { + err := c.updater.Update() + if err != nil { + c.ctx.Logger(PKG).Error("config update failed", "error", err) + } +} + +func (c *cacheImpl) PluginNames() []string { + c.lock.RLock() + defer c.lock.RUnlock() + + return utils.StringMapKeys(c.plugins) +} + +func (c *cacheImpl) GetPlugin(name string) plugin.Plugin { + c.Update() + c.lock.RLock() + defer c.lock.RUnlock() + + p, ok := c.plugins[name] + if ok { + return p + } + return nil +} + +func (c *cacheImpl) add(name string, desc *internal.Descriptor, path string, errmsg string, list *errors.ErrorList) { + c.plugins[name] = plugin.NewPlugin(name, path, c.configs[name], desc, errmsg) + if errmsg != "" && list != nil { + list.Add(fmt.Errorf("%s: %s", name, errmsg)) + } +} + +func (c *cacheImpl) scan(path string) error { + fs := osfs.New() + entries, err := vfs.ReadDir(fs, path) + if err != nil { + return err + } + list := errors.ErrListf("scanning %q", path) + for _, fi := range entries { + if fi.Mode()&0o001 != 0 { + execpath := filepath.Join(path, fi.Name()) + config := c.configs[fi.Name()] + result, err := plugin.Exec(execpath, config, nil, nil, info.NAME) + if err != nil { + c.add(fi.Name(), nil, execpath, err.Error(), list) + continue + } + + // TODO: Version handling by scheme + var desc internal.Descriptor + if err = json.Unmarshal(result, &desc); err != nil { + c.add(fi.Name(), nil, execpath, fmt.Sprintf("cannot unmarshal plugin descriptor: %s", err.Error()), list) + continue + } + + if desc.PluginName != fi.Name() { + c.add(fi.Name(), nil, execpath, fmt.Sprintf("nmatching plugin name %q", desc.PluginName), list) + continue + } + c.add(desc.PluginName, &desc, execpath, "", nil) + } + } + return list.Result() +} + +func (c *cacheImpl) ConfigurePlugin(name string, config json.RawMessage) { + c.lock.Lock() + defer c.lock.Unlock() + + c.configs[name] = config + if p := c.plugins[name]; p != nil { + p.SetConfig(config) + } +} + +// RegisterExtensions registers all the extension provided the found plugin +// at the given context. If no context is given, the cache context is used. +func (c *cacheImpl) RegisterExtensions(ctx ocm.Context) error { + c.lock.RLock() + defer c.lock.RUnlock() + + if ctx == nil { + ctx = c.ctx + } + for _, p := range c.plugins { + if !p.IsValid() { + continue + } + for _, m := range p.GetDescriptor().AccessMethods { + name := m.Name + if m.Version != "" { + name = name + runtime.VersionSeparator + m.Version + } + ctx.AccessMethods().Register(name, access.NewType(name, m.Long, p)) + } + } + return nil +} diff --git a/pkg/contexts/ocm/plugin/config/type.go b/pkg/contexts/ocm/plugin/config/type.go new file mode 100644 index 000000000..17ecf3396 --- /dev/null +++ b/pkg/contexts/ocm/plugin/config/type.go @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package config + +import ( + "encoding/json" + + "github.com/open-component-model/ocm/pkg/contexts/config" + cfgcpi "github.com/open-component-model/ocm/pkg/contexts/config/cpi" + "github.com/open-component-model/ocm/pkg/runtime" +) + +const ( + ConfigType = "plugin" + cfgcpi.OCM_CONFIG_TYPE_SUFFIX + ConfigTypeV1 = ConfigType + runtime.VersionSeparator + "v1" +) + +func init() { + cfgcpi.RegisterConfigType(ConfigType, cfgcpi.NewConfigType(ConfigType, &Config{})) + cfgcpi.RegisterConfigType(ConfigTypeV1, cfgcpi.NewConfigType(ConfigTypeV1, &Config{})) +} + +// Config describes a memory based config interface. +type Config struct { + runtime.ObjectVersionedType `json:",inline"` + Plugin string `json:"plugin"` + Config json.RawMessage `json:"config"` +} + +// New creates a new memory ConfigSpec. +func New() *Config { + return &Config{ + ObjectVersionedType: runtime.NewVersionedObjectType(ConfigType), + } +} + +func (a *Config) GetType() string { + return ConfigType +} + +func (a *Config) ApplyTo(ctx config.Context, target interface{}) error { + t, ok := target.(Target) + if !ok { + return config.ErrNoContext(ConfigType) + } + t.ConfigurePlugin(a.Plugin, a.Config) + return nil +} + +type Target interface { + ConfigurePlugin(name string, config json.RawMessage) +} diff --git a/pkg/contexts/ocm/plugin/interface.go b/pkg/contexts/ocm/plugin/interface.go new file mode 100644 index 000000000..244436b89 --- /dev/null +++ b/pkg/contexts/ocm/plugin/interface.go @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package plugin + +import ( + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/internal" +) + +type ( + Descriptor = internal.Descriptor + AccessSpecInfo = internal.AccessSpecInfo +) diff --git a/pkg/contexts/ocm/plugin/internal/access.go b/pkg/contexts/ocm/plugin/internal/access.go new file mode 100644 index 000000000..684f377da --- /dev/null +++ b/pkg/contexts/ocm/plugin/internal/access.go @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package internal + +import ( + "github.com/open-component-model/ocm/pkg/contexts/credentials" +) + +type AccessSpecInfo struct { + Short string + MediaType string + Hint string + ConsumerId credentials.ConsumerIdentity +} diff --git a/pkg/contexts/ocm/plugin/internal/descriptor.go b/pkg/contexts/ocm/plugin/internal/descriptor.go new file mode 100644 index 000000000..0707d42e4 --- /dev/null +++ b/pkg/contexts/ocm/plugin/internal/descriptor.go @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package internal + +const VERSION = "v1" + +type Descriptor struct { + Version string `json:"version,omitempty"` + PluginName string `json:"pluginName"` + PluginVersion string `json:"pluginVersion"` + Short string `json:"shortDescription"` + Long string `json:"description"` + + AccessMethods []AccessMethodDescriptor `json:"accessMethods,omitempty"` +} + +type AccessMethodDescriptor struct { + Name string `json:"name"` + Version string `json:"version,omitempty"` + Short string `json:"shortDescription"` + Long string `json:"description"` +} diff --git a/pkg/contexts/ocm/plugin/plugin.go b/pkg/contexts/ocm/plugin/plugin.go new file mode 100644 index 000000000..2cf6e7543 --- /dev/null +++ b/pkg/contexts/ocm/plugin/plugin.go @@ -0,0 +1,158 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package plugin + +import ( + "encoding/json" + "fmt" + "io" + "os/exec" + + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/internal" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds" + "github.com/open-component-model/ocm/pkg/errors" +) + +type Plugin = *pluginImpl + +// //nolint: errname // is no error. +type pluginImpl struct { + name string + config json.RawMessage + descriptor *internal.Descriptor + path string + error string +} + +func NewPlugin(name string, path string, config json.RawMessage, desc *Descriptor, errmsg string) Plugin { + return &pluginImpl{ + name: name, + path: path, + config: config, + descriptor: desc, + error: errmsg, + } +} + +func (p *pluginImpl) GetDescriptor() *internal.Descriptor { + return p.descriptor +} + +func (p *pluginImpl) Name() string { + return p.name +} + +func (p *pluginImpl) Path() string { + return p.path +} + +func (p *pluginImpl) Version() string { + if !p.IsValid() { + return "-" + } + return p.descriptor.PluginVersion +} + +func (p *pluginImpl) IsValid() bool { + return p.descriptor != nil +} + +func (p *pluginImpl) Error() string { + return p.error +} + +func (p *pluginImpl) SetConfig(data json.RawMessage) { + p.config = data +} + +func (p *pluginImpl) GetAccessMethodDescriptor(name, version string) *internal.AccessMethodDescriptor { + if !p.IsValid() { + return nil + } + + var fallback internal.AccessMethodDescriptor + fallbackFound := false + for _, m := range p.descriptor.AccessMethods { + if m.Name == name { + if m.Version == version { + return &m + } + if m.Version == "" || m.Version == "v1" { + fallback = m + fallbackFound = true + } + } + } + if fallbackFound && (version == "" || version == "v1") { + return &fallback + } + return nil +} + +func (p *pluginImpl) Message() string { + if p.IsValid() { + return p.descriptor.Short + } + if p.error != "" { + return "Error: " + p.error + } + return "unknown state" +} + +func (p *pluginImpl) Validate(spec []byte) (*ppi.AccessSpecInfo, error) { + result, err := p.Exec(nil, nil, "accessmethod", "validate", string(spec)) + if err != nil { + return nil, errors.Wrapf(err, "plugin %s", p.Name()) + } + + var info ppi.AccessSpecInfo + err = json.Unmarshal(result, &info) + if err != nil { + return nil, errors.Wrapf(err, "plugin %s: cannot unmarshal access spec info", p.Name()) + } + return &info, nil +} + +func (p *pluginImpl) Exec(r io.Reader, w io.Writer, args ...string) ([]byte, error) { + return Exec(p.path, p.config, r, w, args...) +} + +func Exec(execpath string, config json.RawMessage, r io.Reader, w io.Writer, args ...string) ([]byte, error) { + if len(config) > 0 { + args = append([]string{"-c", string(config)}, args...) + } + cmd := exec.Command(execpath, args...) + + stdout := w + if w == nil { + stdout = LimitBuffer(LIMIT) + } + + stderr := LimitBuffer(LIMIT) + + cmd.Stdin = r + cmd.Stdout = stdout + cmd.Stderr = stderr + + err := cmd.Run() + if err != nil { + var result cmds.Error + var msg string + if err := json.Unmarshal(stderr.Bytes(), &result); err == nil { + msg = result.Error + } else { + msg = fmt.Sprintf("[%s]", string(stderr.Bytes())) + } + return nil, fmt.Errorf("%s", msg) + } + if l, ok := stdout.(*LimitedBuffer); ok { + if l.Exceeded() { + return nil, fmt.Errorf("stdout limit exceeded") + } + return l.Bytes(), nil + } + return nil, nil +} diff --git a/pkg/contexts/ocm/plugin/plugin_test.go b/pkg/contexts/ocm/plugin/plugin_test.go new file mode 100644 index 000000000..a4b1d86aa --- /dev/null +++ b/pkg/contexts/ocm/plugin/plugin_test.go @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package plugin_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/open-component-model/ocm/pkg/contexts/credentials" + "github.com/open-component-model/ocm/pkg/contexts/ocm" + access "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/plugin" + "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/plugincacheattr" + "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/plugindirattr" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/cache" +) + +var _ = Describe("setup plugin cache", func() { + var ctx ocm.Context + var registry cache.Cache + + BeforeEach(func() { + ctx = ocm.New() + plugindirattr.Set(ctx, "testdata") + registry = plugincacheattr.Get(ctx) + }) + + It("finds plugin", func() { + p := registry.GetPlugin("test") + Expect(p).NotTo(BeNil()) + Expect(p.GetDescriptor().Short).To(Equal("a test plugin")) + }) + + It("registers access methods", func() { + p := registry.GetPlugin("test") + Expect(p).NotTo(BeNil()) + Expect(len(p.GetDescriptor().AccessMethods)).To(Equal(2)) + Expect(registry.RegisterExtensions(nil)).To(Succeed()) + t := ctx.AccessMethods().GetAccessType("test") + Expect(t).NotTo(BeNil()) + raw := ` +type: test +someattr: value +` + s, err := ctx.AccessSpecForConfig([]byte(raw), nil) + Expect(err).To(Succeed()) + spec := s.(*access.AccessSpec) + h := spec.Handler() + info, err := h.Info(spec) + Expect(err).To(Succeed()) + Expect(info).To(Equal(&plugin.AccessSpecInfo{ + Short: "a test", + MediaType: "plain/text", + Hint: "testfile", + ConsumerId: credentials.ConsumerIdentity{ + "type": "test", + "hostname": "localhost", + }, + })) + }) +}) diff --git a/pkg/contexts/ocm/plugin/ppi/accessmethod.go b/pkg/contexts/ocm/plugin/ppi/accessmethod.go new file mode 100644 index 000000000..e0d972344 --- /dev/null +++ b/pkg/contexts/ocm/plugin/ppi/accessmethod.go @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package ppi + +import ( + "github.com/open-component-model/ocm/pkg/runtime" +) + +type AccessSpec runtime.VersionedTypedObject + +type AccessSpecProvider func() AccessSpec diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/cmd.go new file mode 100644 index 000000000..faf88a2b6 --- /dev/null +++ b/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/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 accessmethod + +import ( + "github.com/spf13/cobra" + + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/get" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/put" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/validate" +) + +const NAME = "accessmethod" + +func New(p ppi.Plugin) *cobra.Command { + cmd := &cobra.Command{ + Use: NAME, + Short: "access method operations", + Long: "", + } + + cmd.AddCommand(validate.New(p)) + cmd.AddCommand(get.New(p)) + cmd.AddCommand(put.New(p)) + return cmd +} diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/get/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/get/cmd.go new file mode 100644 index 000000000..8ab4c211c --- /dev/null +++ b/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/get/cmd.go @@ -0,0 +1,80 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package get + +import ( + "fmt" + "io" + "os" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + "github.com/open-component-model/ocm/pkg/cobrautils/flag" + "github.com/open-component-model/ocm/pkg/contexts/credentials" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" + "github.com/open-component-model/ocm/pkg/errors" +) + +const Name = "get" + +func New(p ppi.Plugin) *cobra.Command { + opts := Options{} + + cmd := &cobra.Command{ + Use: Name + " ", + Short: "get blob", + Long: "", + Args: cobra.ExactArgs(1), + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return opts.Complete(args) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return Command(p, cmd, &opts) + }, + } + opts.AddFlags(cmd.Flags()) + return cmd +} + +type Options struct { + Credentials credentials.DirectCredentials + Specification []byte +} + +func (o *Options) AddFlags(fs *pflag.FlagSet) { + flag.YAMLVarP(fs, &o.Credentials, "credentials", "c", nil, "credentials") + flag.StringMapVarPA(fs, &o.Credentials, "credential", "C", nil, "dedicated credential value") +} + +func (o *Options) Complete(args []string) error { + o.Specification = []byte(args[0]) + + fmt.Fprintf(os.Stderr, "credentials: %s\n", o.Credentials.String()) + return nil +} + +func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error { + spec, err := p.DecodeAccessSpecification(opts.Specification) + if err != nil { + return err + } + + m := p.GetAccessMethod(spec.GetKind(), spec.GetVersion()) + if m == nil { + return errors.ErrUnknown(errors.KIND_ACCESSMETHOD, spec.GetType()) + } + _, err = m.ValidateSpecification(p, spec) + if err != nil { + return err + } + r, err := m.Reader(p, spec, opts.Credentials) + if err != nil { + return err + } + _, err = io.Copy(os.Stdout, r) + r.Close() + return err +} diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/put/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/put/cmd.go new file mode 100644 index 000000000..986841ea1 --- /dev/null +++ b/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/put/cmd.go @@ -0,0 +1,104 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package put + +import ( + "encoding/json" + "io" + "os" + "strings" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + "github.com/open-component-model/ocm/pkg/cobrautils/flag" + "github.com/open-component-model/ocm/pkg/contexts/credentials" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" + "github.com/open-component-model/ocm/pkg/errors" + "github.com/open-component-model/ocm/pkg/runtime" +) + +const Name = "put" + +func New(p ppi.Plugin) *cobra.Command { + opts := Options{} + + cmd := &cobra.Command{ + Use: Name + " ", + Short: "put blob", + Long: "", + Args: cobra.ExactArgs(1), + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return opts.Complete(args) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return Command(p, cmd, &opts) + }, + } + opts.AddFlags(cmd.Flags()) + return cmd +} + +type Options struct { + Credentials credentials.DirectCredentials + + MediaType string + MethodName string + MethodVersion string + + Hint string +} + +func (o *Options) AddFlags(fs *pflag.FlagSet) { + flag.YAMLVarP(fs, &o.Credentials, "credentials", "c", nil, "credentials") + flag.StringMapVarPA(fs, &o.Credentials, "credential", "C", nil, "dedicated credential value") + fs.StringVarP(&o.MediaType, "mediaType", "m", "", "media type of input blob") + fs.StringVarP(&o.Hint, "hint", "h", "", "reference hint for storing blob") +} + +func (o *Options) Complete(args []string) error { + fields := strings.Split(args[0], runtime.VersionSeparator) + o.MethodName = fields[0] + if len(fields) > 1 { + o.MethodVersion = fields[1] + } + if len(fields) > 2 { + return errors.ErrInvalid(errors.KIND_ACCESSMETHOD, args[0]) + } + return nil +} + +func (o *Options) Method() string { + if o.MethodVersion == "" { + return o.MethodName + } + return o.MethodName + runtime.VersionSeparator + o.MethodVersion +} + +func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error { + m := p.GetAccessMethod(opts.MethodName, opts.MethodVersion) + if m == nil { + return errors.ErrUnknown(errors.KIND_ACCESSMETHOD, opts.Method()) + } + w, h, err := m.Writer(p, opts.MediaType, opts.Credentials) + if err != nil { + return err + } + _, err = io.Copy(w, os.Stdin) + if err != nil { + w.Close() + return err + } + err = w.Close() + if err != nil { + return err + } + spec := h() + data, err := json.Marshal(spec) + if err == nil { + cmd.Printf("%s\n", string(data)) + } + return err +} diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/validate/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/validate/cmd.go new file mode 100644 index 000000000..e9aaeee33 --- /dev/null +++ b/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/validate/cmd.go @@ -0,0 +1,77 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package validate + +import ( + "encoding/json" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + "github.com/open-component-model/ocm/pkg/contexts/credentials" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" + "github.com/open-component-model/ocm/pkg/errors" +) + +func New(p ppi.Plugin) *cobra.Command { + opts := Options{} + + cmd := &cobra.Command{ + Use: "validate ", + Short: "validate access specification", + Long: "", + Args: cobra.ExactArgs(1), + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return opts.Complete(args) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return Command(p, cmd, &opts) + }, + } + opts.AddFlags(cmd.Flags()) + return cmd +} + +type Options struct { + Specification []byte +} + +func (o *Options) AddFlags(fs *pflag.FlagSet) { +} + +func (o *Options) Complete(args []string) error { + o.Specification = []byte(args[0]) + return nil +} + +type Result struct { + MediaType string `json:"mediaType"` + Short string `json:"description"` + Hint string `json:"hint"` + ConsumerId credentials.ConsumerIdentity `json:"consumerId"` +} + +func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error { + spec, err := p.DecodeAccessSpecification(opts.Specification) + if err != nil { + return err + } + + m := p.GetAccessMethod(spec.GetKind(), spec.GetVersion()) + if m == nil { + return errors.ErrUnknown(errors.KIND_ACCESSMETHOD, spec.GetType()) + } + info, err := m.ValidateSpecification(p, spec) + if err != nil { + return err + } + result := Result{MediaType: info.MediaType, ConsumerId: info.ConsumerId, Hint: info.Hint} + data, err := json.Marshal(result) + if err != nil { + return err + } + cmd.Printf("%s\n", string(data)) + return nil +} diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/app.go b/pkg/contexts/ocm/plugin/ppi/cmds/app.go new file mode 100644 index 000000000..cc059b11e --- /dev/null +++ b/pkg/contexts/ocm/plugin/ppi/cmds/app.go @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package cmds + +import ( + "encoding/json" + + "github.com/spf13/cobra" + + "github.com/open-component-model/ocm/pkg/cobrautils" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/info" +) + +type PluginCommand struct { + command *cobra.Command + plugin ppi.Plugin +} + +func NewPluginCommand(p ppi.Plugin) *PluginCommand { + pcmd := &PluginCommand{ + plugin: p, + } + cmd := &cobra.Command{ + Use: p.Name() + " ", + Short: "OCM plugin " + p.Name(), + Long: "OCM plugin " + p.Name(), + Version: p.Version(), + TraverseChildren: true, + SilenceUsage: true, + DisableFlagsInUseLine: true, + SilenceErrors: true, + } + + cobrautils.TweakCommand(cmd, nil) + + cmd.AddCommand(info.New(p)) + cmd.AddCommand(accessmethod.New(p)) + + cmd.InitDefaultHelpCmd() + var help *cobra.Command + for _, c := range cmd.Commands() { + if c.Name() == "help" { + help = c + break + } + } + // help.Use="help " + help.DisableFlagsInUseLine = true + + p.Options().AddFlags(cmd.Flags()) + pcmd.command = cmd + return pcmd +} + +type Error struct { + Error string `json:"error"` +} + +func (p *PluginCommand) Execute(args []string) error { + p.command.SetArgs(args) + err := p.command.Execute() + if err != nil { + result, err2 := json.Marshal(Error{err.Error()}) + if err2 != nil { + return err2 + } + p.command.PrintErrln(string(result)) + } + return err +} diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/info/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/info/cmd.go new file mode 100644 index 000000000..b07ea2db1 --- /dev/null +++ b/pkg/contexts/ocm/plugin/ppi/cmds/info/cmd.go @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package info + +import ( + "encoding/json" + + "github.com/spf13/cobra" + + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" +) + +const NAME = "info" + +func New(p ppi.Plugin) *cobra.Command { + return &cobra.Command{ + Use: NAME, + Short: "show plugin descriptor", + Long: "", + Args: cobra.MaximumNArgs(0), + RunE: func(cmd *cobra.Command, args []string) error { + data, err := json.Marshal(p.Descriptor()) + if err != nil { + return err + } + cmd.Printf("%s\n", string(data)) + return nil + }, + } +} diff --git a/pkg/contexts/ocm/plugin/ppi/doc.go b/pkg/contexts/ocm/plugin/ppi/doc.go new file mode 100644 index 000000000..07b12881b --- /dev/null +++ b/pkg/contexts/ocm/plugin/ppi/doc.go @@ -0,0 +1,7 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +// ppi provides the plugin programming interface. +// It contains everything requirted to implement an OCM plugin +package ppi diff --git a/pkg/contexts/ocm/plugin/ppi/interface.go b/pkg/contexts/ocm/plugin/ppi/interface.go new file mode 100644 index 000000000..f6230205e --- /dev/null +++ b/pkg/contexts/ocm/plugin/ppi/interface.go @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package ppi + +import ( + "io" + + "github.com/open-component-model/ocm/pkg/contexts/credentials" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/internal" + "github.com/open-component-model/ocm/pkg/runtime" +) + +type ( + Descriptor = internal.Descriptor + AccessSpecInfo = internal.AccessSpecInfo +) + +const KIND_PLUGIN = "plugin" + +type Plugin interface { + Name() string + Version() string + Descriptor() internal.Descriptor + + RegisterAccessMethod(m AccessMethod) error + DecodeAccessSpecification(data []byte) (AccessSpec, error) + GetAccessMethod(name string, version string) AccessMethod + + Options() *Options +} + +type AccessMethod interface { + runtime.TypedObjectDecoder + + Name() string + Version() string + + ValidateSpecification(p Plugin, spec AccessSpec) (info *AccessSpecInfo, err error) + Reader(p Plugin, spec AccessSpec, creds credentials.Credentials) (io.ReadCloser, error) + Writer(p Plugin, mediatype string, creds credentials.Credentials) (io.WriteCloser, AccessSpecProvider, error) +} diff --git a/pkg/contexts/ocm/plugin/ppi/options.go b/pkg/contexts/ocm/plugin/ppi/options.go new file mode 100644 index 000000000..5f9057806 --- /dev/null +++ b/pkg/contexts/ocm/plugin/ppi/options.go @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package ppi + +import ( + "encoding/json" + + "github.com/spf13/pflag" + + "github.com/open-component-model/ocm/pkg/cobrautils/flag" +) + +type Options struct { + Config json.RawMessage +} + +func (o *Options) AddFlags(fs *pflag.FlagSet) { + flag.YAMLVarP(fs, &o.Config, "config", "c", nil, "plugin configuration") +} diff --git a/pkg/contexts/ocm/plugin/ppi/plugin.go b/pkg/contexts/ocm/plugin/ppi/plugin.go new file mode 100644 index 000000000..64a98cfa2 --- /dev/null +++ b/pkg/contexts/ocm/plugin/ppi/plugin.go @@ -0,0 +1,97 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package ppi + +import ( + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/internal" + "github.com/open-component-model/ocm/pkg/errors" + "github.com/open-component-model/ocm/pkg/runtime" +) + +type plugin struct { + name string + version string + descriptor internal.Descriptor + options Options + + methods map[string]AccessMethod + accessScheme runtime.Scheme +} + +func NewPlugin(name string, version string) Plugin { + var rt runtime.VersionedTypedObject + return &plugin{ + name: name, + version: version, + methods: map[string]AccessMethod{}, + accessScheme: runtime.MustNewDefaultScheme(&rt, &runtime.UnstructuredVersionedTypedObject{}, false, nil), + descriptor: internal.Descriptor{ + Version: internal.VERSION, + PluginName: name, + PluginVersion: version, + }, + } +} + +func (p *plugin) Name() string { + return p.name +} + +func (p *plugin) Version() string { + return p.version +} + +func (p *plugin) Descriptor() internal.Descriptor { + return p.descriptor +} + +func (p *plugin) Options() *Options { + return &p.options +} + +func (p *plugin) RegisterAccessMethod(m AccessMethod) error { + if p.GetAccessMethod(m.Name(), m.Version()) != nil { + n := m.Name() + if m.Version() != "" { + n += "/" + m.Version() + } + return errors.ErrAlreadyExists(errors.KIND_ACCESSMETHOD, n) + } + + vers := m.Version() + if vers == "" { + meth := internal.AccessMethodDescriptor{ + Name: m.Name(), + } + p.descriptor.AccessMethods = append(p.descriptor.AccessMethods, meth) + p.accessScheme.RegisterByDecoder(m.Name(), m) + vers = "v1" + p.methods[m.Name()] = m + } + meth := internal.AccessMethodDescriptor{ + Name: m.Name(), + Version: vers, + } + p.descriptor.AccessMethods = append(p.descriptor.AccessMethods, meth) + p.accessScheme.RegisterByDecoder(m.Name()+"/"+vers, m) + p.methods[m.Name()+"/"+vers] = m + return nil +} + +func (p *plugin) DecodeAccessSpecification(data []byte) (AccessSpec, error) { + o, err := p.accessScheme.Decode(data, nil) + if err != nil { + return nil, err + } + return o.(AccessSpec), nil +} + +func (p *plugin) GetAccessMethod(name string, version string) AccessMethod { + n := name + if version != "" { + n += "/" + version + } + return p.methods[n] +} diff --git a/pkg/contexts/ocm/plugin/ppi/utils.go b/pkg/contexts/ocm/plugin/ppi/utils.go new file mode 100644 index 000000000..163f5ce6b --- /dev/null +++ b/pkg/contexts/ocm/plugin/ppi/utils.go @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package ppi + +import ( + "github.com/open-component-model/ocm/pkg/runtime" +) + +type decoder runtime.TypedObjectDecoder + +type AccessMethodBase struct { + decoder + name string + version string +} + +func MustNewAccessMethodBase(name, version string, proto AccessSpec) AccessMethodBase { + decoder, err := runtime.NewDirectDecoder(proto) + if err != nil { + panic(err) + } + + return AccessMethodBase{ + decoder: decoder, + name: name, + version: version, + } +} + +func (b *AccessMethodBase) Name() string { + return b.name +} + +func (b *AccessMethodBase) Version() string { + return b.version +} diff --git a/pkg/contexts/ocm/plugin/suite_test.go b/pkg/contexts/ocm/plugin/suite_test.go new file mode 100644 index 000000000..357349401 --- /dev/null +++ b/pkg/contexts/ocm/plugin/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 plugin_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestConfig(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Plugin Test Suite") +} diff --git a/pkg/contexts/ocm/plugin/testdata/test b/pkg/contexts/ocm/plugin/testdata/test new file mode 100755 index 000000000..2f997edaa --- /dev/null +++ b/pkg/contexts/ocm/plugin/testdata/test @@ -0,0 +1,39 @@ +#!/bin/bash + +# SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +# +# SPDX-License-Identifier: Apache-2.0 + +NAME="$(basename "$0")" + +Error() { + echo '{ "error": "'$1'" }' >&2 + exit 1 +} + +Info() { + echo '{"version":"v1","pluginName":"'$NAME'","pluginVersion":"v1","shortDescription":"a test plugin","description":"a test plugin with access method test","accessMethods":[{"name":"test","shortDescription":"test access","description":""},{"name":"test","version":"v1","shortDescription":"test access","description":""}]} +' +} + +Get() { + echo "test content" +} + +Validate() { + echo '{"short":"a test","mediaType":"plain/text","description":"","hint":"testfile","consumerId":{"hostname":"localhost","type":"test"}}' +} + +AccessMethod() { + case "$1" in + get) Get "${@:2}";; + validate) Validate "${@:2}";; + *) Error "invalid accessmethod command $1";; + esac +} + +case "$1" in + info) Info;; + accessmethod) AccessMethod "${@:2}";; + *) Error "invalid command $1";; +esac diff --git a/pkg/contexts/ocm/plugin/utils.go b/pkg/contexts/ocm/plugin/utils.go new file mode 100644 index 000000000..f99f898b5 --- /dev/null +++ b/pkg/contexts/ocm/plugin/utils.go @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package plugin + +import ( + "bytes" + "io" + + "github.com/opencontainers/go-digest" + + "github.com/open-component-model/ocm/pkg/common/accessio" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/get" +) + +const LIMIT = int64(8196) + +// LimitWriter returns a Writer that writes to w +// but stops with EOF after n bytes. +// The underlying implementation is a *LimitedWriter. +func LimitWriter(w io.Writer, n int64) io.Writer { return &LimitedWriter{w, n} } + +// A LimitedWriter writes to W but limits the amount of +// data written to just N bytes. Each call to Write +// updates N to reflect the new amount remaining. +// Write returns EOF when N <= 0 or when the underlying W returns EOF. +type LimitedWriter struct { + W io.Writer // underlying reader + N int64 // max bytes remaining +} + +func (l *LimitedWriter) Write(p []byte) (n int, err error) { + if l.N <= 0 { + return 0, io.EOF + } + if int64(len(p)) > l.N { + p = p[0:l.N] + } + n, err = l.W.Write(p) + l.N -= int64(n) + return +} + +func LimitBuffer(n int64) *LimitedBuffer { + buf := &LimitedBuffer{max: n} + buf.LimitedWriter = &LimitedWriter{&buf.buffer, n + 1} + return buf +} + +type LimitedBuffer struct { + *LimitedWriter + max int64 + buffer bytes.Buffer +} + +func (b *LimitedBuffer) Exceeded() bool { + return b.LimitedWriter.N > b.max +} + +func (b *LimitedBuffer) Bytes() []byte { + return b.buffer.Bytes() +} + +//////////////////////////////////////////////////////////////////////////////// + +type AccessDataWriter struct { + plugin Plugin + acctype string +} + +func NewAccessDataWriter(p Plugin, acctype string) *AccessDataWriter { + return &AccessDataWriter{p, acctype} +} + +func (d *AccessDataWriter) WriteTo(w accessio.Writer) (int64, digest.Digest, error) { + dw := accessio.NewDefaultDigestWriter(accessio.NopWriteCloser(w)) + _, err := d.plugin.Exec(nil, dw, accessmethod.NAME, get.Name, d.acctype) + if err != nil { + return accessio.BLOB_UNKNOWN_SIZE, accessio.BLOB_UNKNOWN_DIGEST, err + } + return dw.Size(), dw.Digest(), nil +} diff --git a/pkg/contexts/ocm/transfer/transferhandler/standard/handler_test.go b/pkg/contexts/ocm/transfer/transferhandler/standard/handler_test.go index ea2e557a6..f53e7239c 100644 --- a/pkg/contexts/ocm/transfer/transferhandler/standard/handler_test.go +++ b/pkg/contexts/ocm/transfer/transferhandler/standard/handler_test.go @@ -99,7 +99,10 @@ var _ = Describe("Transfer handler", func() { tgt, err := ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0700, accessio.FormatDirectory, env) Expect(err).To(Succeed()) defer tgt.Close() - handler, err := standard.New(standard.ResourcesByValue()) + opts := &standard.Options{} + opts.SetResourcesByValue(true) + handler := standard.NewDefaultHandler(opts) + //handler, err := standard.New(standard.ResourcesByValue()) Expect(err).To(Succeed()) err = transfer.TransferVersion(nil, nil, cv, tgt, handler) Expect(err).To(Succeed()) diff --git a/pkg/contexts/ocm/utils/configure.go b/pkg/contexts/ocm/utils/configure.go index 68a750ef5..053463532 100644 --- a/pkg/contexts/ocm/utils/configure.go +++ b/pkg/contexts/ocm/utils/configure.go @@ -26,6 +26,8 @@ import ( const DEFAULT_OCM_CONFIG = ".ocmconfig" +const DEFAULT_OCM_CONFIG_DIR = ".ocm" + func Configure(ctx ocm.Context, path string, fss ...vfs.FileSystem) (ocm.Context, error) { fs := accessio.FileSystem(fss...) if ctx == nil { @@ -38,7 +40,7 @@ func Configure(ctx ocm.Context, path string, fss ...vfs.FileSystem) (ocm.Context if ok, err := vfs.FileExists(fs, cfg); ok && err == nil { path = cfg } else { - cfg := h + "/.ocm/ocmconfig" + cfg := h + "/" + DEFAULT_OCM_CONFIG_DIR + "/ocmconfig" if ok, err := vfs.FileExists(fs, cfg); ok && err == nil { path = cfg } diff --git a/pkg/errors/error.go b/pkg/errors/error.go index 304375261..29d8da59c 100644 --- a/pkg/errors/error.go +++ b/pkg/errors/error.go @@ -122,6 +122,7 @@ type errinfo struct { format ErrorFormatter kind string elem *string + ctxkind string ctx string } @@ -136,6 +137,13 @@ func newErrInfo(fmt ErrorFormatter, spec ...string) errinfo { format: fmt, } + if len(spec) > 3 { + e.kind = spec[0] + e.elem = &spec[1] + e.ctxkind = spec[2] + e.ctx = spec[3] + return e + } if len(spec) > 2 { e.kind = spec[0] e.elem = &spec[1] @@ -147,6 +155,7 @@ func newErrInfo(fmt ErrorFormatter, spec ...string) errinfo { e.elem = &spec[1] return e } + if len(spec) > 0 { e.elem = &spec[0] } @@ -154,7 +163,7 @@ func newErrInfo(fmt ErrorFormatter, spec ...string) errinfo { } func (e *errinfo) Error() string { - msg := e.format.Format(e.kind, e.elem, e.ctx) + msg := e.format.Format(e.kind, e.elem, e.ctxkind, e.ctx) if e.wrapped != nil { return msg + ": " + e.wrapped.Error() } @@ -173,6 +182,10 @@ func (e *errinfo) Kind() string { return e.kind } +func (e *errinfo) CtxKind() string { + return e.ctxkind +} + func (e *errinfo) Ctx() string { return e.ctx } diff --git a/pkg/errors/format.go b/pkg/errors/format.go index ab3ac951e..15d456cd2 100644 --- a/pkg/errors/format.go +++ b/pkg/errors/format.go @@ -5,7 +5,7 @@ package errors type ErrorFormatter interface { - Format(kind string, elem *string, ctx string) string + Format(kind string, elem *string, ctxkind string, ctx string) string } type defaultFormatter struct { @@ -25,8 +25,11 @@ func NewDefaultFormatter(verb, msg, preposition string) ErrorFormatter { } } -func (f *defaultFormatter) Format(kind string, elem *string, ctx string) string { +func (f *defaultFormatter) Format(kind string, elem *string, ctxkind string, ctx string) string { if ctx != "" { + if ctxkind != "" { + ctx = ctxkind + " " + ctx + } ctx = " " + f.preposition + " " + ctx } elems := "" diff --git a/pkg/testutils/utils.go b/pkg/testutils/utils.go index 73d1702b8..5468d2bc7 100644 --- a/pkg/testutils/utils.go +++ b/pkg/testutils/utils.go @@ -17,7 +17,7 @@ func Close(c io.Closer, msg ...interface{}) { if err != nil { switch len(msg) { case 0: - Expect(err).To(Succeed()) + ExpectWithOffset(1, err).To(Succeed()) case 1: Fail(fmt.Sprintf("%s: %s", msg[0], err), 1) default: From bcfaddabbc127c746fed8cf2868b02da0fcc80d9 Mon Sep 17 00:00:00 2001 From: Uwe Krueger Date: Sun, 30 Oct 2022 11:57:49 +0100 Subject: [PATCH 2/6] support dynamic CLI help --- Makefile | 2 +- cmds/demoplugin/accessmethods/demo.go | 14 +- cmds/demoplugin/main.go | 6 + cmds/demoplugin/uploaders/demo.go | 43 ++++ .../{accessmethods => uploaders}/writer.go | 22 +- cmds/ocm/app/app.go | 2 + cmds/ocm/commands/ocmcmds/cmd.go | 2 + .../ocmcmds/common/inputs/inputtype.go | 3 +- cmds/ocm/commands/ocmcmds/plugins/get/cmd.go | 11 +- .../ocmcmds/resourceconfig/add/cmd.go | 17 +- .../ocm/commands/ocmcmds/resources/add/cmd.go | 23 +- .../ocmcmds/resources/add/provider.go | 2 +- .../commands/ocmcmds/sourceconfig/add/cmd.go | 17 +- cmds/ocm/commands/ocmcmds/sources/add/cmd.go | 11 +- cmds/ocm/pkg/template/registry.go | 8 +- cmds/ocm/pkg/template/template.go | 1 - cmds/ocm/pkg/utils/command.go | 110 +++++++- docs/reference/ocm_add_references.md | 3 - .../ocm_add_resource-configuration.md | 235 +++++++++--------- docs/reference/ocm_add_resources.md | 233 +++++++++-------- .../reference/ocm_add_source-configuration.md | 232 +++++++++-------- docs/reference/ocm_add_sources.md | 232 +++++++++-------- docs/reference/ocm_configfile.md | 9 - docs/reference/ocm_create_componentarchive.md | 2 - docs/reference/ocm_get_componentversions.md | 2 - docs/reference/ocm_get_credentials.md | 5 - docs/reference/ocm_sign_componentversions.md | 7 - go.mod | 4 +- go.sum | 4 +- pkg/contexts/ocm/accessmethods/github/cli.go | 4 +- .../ocm/accessmethods/github/method.go | 8 +- .../ocm/accessmethods/localblob/cli.go | 4 +- .../ocm/accessmethods/localblob/method.go | 4 +- .../ocm/accessmethods/localfsblob/method.go | 4 +- .../ocm/accessmethods/localociblob/method.go | 4 +- pkg/contexts/ocm/accessmethods/none/method.go | 6 +- .../ocm/accessmethods/ociartefact/cli.go | 4 +- .../ocm/accessmethods/ociartefact/method.go | 8 +- pkg/contexts/ocm/accessmethods/ociblob/cli.go | 5 +- .../ocm/accessmethods/ociblob/method.go | 4 +- pkg/contexts/ocm/accessmethods/plugin/type.go | 7 +- pkg/contexts/ocm/accessmethods/s3/cli.go | 4 +- pkg/contexts/ocm/accessmethods/s3/method.go | 4 +- pkg/contexts/ocm/cpi/accessspec_options.go | 64 +++++ pkg/contexts/ocm/cpi/accessspecs_converted.go | 12 +- pkg/contexts/ocm/cpi/accessspecs_simple.go | 38 ++- pkg/contexts/ocm/internal/accesstypes.go | 3 +- pkg/contexts/ocm/plugin/cache/cache.go | 2 +- .../ocm/plugin/internal/descriptor.go | 28 ++- pkg/contexts/ocm/plugin/internal/lookup.go | 63 +++++ .../ocm/plugin/internal/lookup_test.go | 80 ++++++ .../ocm/plugin/internal/suite_test.go | 17 ++ pkg/contexts/ocm/plugin/ppi/accessmethod.go | 13 - .../ocm/plugin/ppi/cmds/accessmethod/cmd.go | 2 - .../plugin/ppi/cmds/accessmethod/get/cmd.go | 2 +- pkg/contexts/ocm/plugin/ppi/cmds/app.go | 6 + .../cmds/{accessmethod/put => upload}/cmd.go | 45 ++-- pkg/contexts/ocm/plugin/ppi/interface.go | 37 ++- pkg/contexts/ocm/plugin/ppi/plugin.go | 78 +++++- pkg/contexts/ocm/plugin/ppi/utils.go | 44 +++- pkg/contexts/ocm/usage.go | 60 +++-- pkg/runtime/versionedtype.go | 8 + pkg/utils/utils.go | 13 +- 63 files changed, 1251 insertions(+), 696 deletions(-) create mode 100644 cmds/demoplugin/uploaders/demo.go rename cmds/demoplugin/{accessmethods => uploaders}/writer.go (67%) create mode 100644 pkg/contexts/ocm/cpi/accessspec_options.go create mode 100644 pkg/contexts/ocm/plugin/internal/lookup.go create mode 100644 pkg/contexts/ocm/plugin/internal/lookup_test.go create mode 100644 pkg/contexts/ocm/plugin/internal/suite_test.go delete mode 100644 pkg/contexts/ocm/plugin/ppi/accessmethod.go rename pkg/contexts/ocm/plugin/ppi/cmds/{accessmethod/put => upload}/cmd.go (67%) diff --git a/Makefile b/Makefile index 3824a4055..e6b89aeb2 100644 --- a/Makefile +++ b/Makefile @@ -42,7 +42,7 @@ build: ${SOURCES} -X github.com/open-component-model/ocm/pkg/version.gitTreeState=$(GIT_TREE_STATE) \ -X github.com/open-component-model/ocm/pkg/version.gitCommit=$(COMMIT) \ -X github.com/open-component-model/ocm/pkg/version.buildDate=$(shell date --rfc-3339=seconds | sed 's/ /T/')" \ - -o bin/demor \ + -o bin/demo \ ./cmds/demoplugin diff --git a/cmds/demoplugin/accessmethods/demo.go b/cmds/demoplugin/accessmethods/demo.go index f981c9f4c..ac1036f88 100644 --- a/cmds/demoplugin/accessmethods/demo.go +++ b/cmds/demoplugin/accessmethods/demo.go @@ -19,6 +19,9 @@ import ( const CONSUMER_TYPE = "demo" +const NAME = "demo" +const VERSION = "v1" + type AccessSpec struct { runtime.ObjectVersionedType `json:",inline"` @@ -34,7 +37,7 @@ var _ ppi.AccessMethod = (*AccessMethod)(nil) func New() ppi.AccessMethod { return &AccessMethod{ - AccessMethodBase: ppi.MustNewAccessMethodBase("demo", "", &AccessSpec{}), + AccessMethodBase: ppi.MustNewAccessMethodBase(NAME, "", &AccessSpec{}, "demo access to temp files", ""), } } @@ -80,12 +83,3 @@ func (a *AccessMethod) Reader(p ppi.Plugin, spec ppi.AccessSpec, creds credentia return os.Open(filepath.Join(os.TempDir(), my.Path)) } - -func (a *AccessMethod) Writer(p ppi.Plugin, mediatype string, creds credentials.Credentials) (io.WriteCloser, ppi.AccessSpecProvider, error) { - file, err := os.CreateTemp(os.TempDir(), "demo.*.blob") - if err != nil { - return nil, nil, err - } - writer := NewWriter(file, mediatype, a.Name(), a.Version()) - return writer, writer.Specification, nil -} diff --git a/cmds/demoplugin/main.go b/cmds/demoplugin/main.go index 8a6dd37ea..2a8f53dc3 100644 --- a/cmds/demoplugin/main.go +++ b/cmds/demoplugin/main.go @@ -8,6 +8,7 @@ import ( "os" "github.com/open-component-model/ocm/cmds/demoplugin/accessmethods" + "github.com/open-component-model/ocm/cmds/demoplugin/uploaders" "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds" "github.com/open-component-model/ocm/pkg/version" @@ -16,7 +17,12 @@ import ( func main() { p := ppi.NewPlugin("demo", version.Get().String()) + p.SetShort("demo plugin") + p.SetLong("plugin providing access to temp files.") + p.RegisterAccessMethod(accessmethods.New()) + u := uploaders.New() + p.RegisterUploader("testArtefact", "", u) err := cmds.NewPluginCommand(p).Execute(os.Args[1:]) if err != nil { os.Exit(1) diff --git a/cmds/demoplugin/uploaders/demo.go b/cmds/demoplugin/uploaders/demo.go new file mode 100644 index 000000000..104372d71 --- /dev/null +++ b/cmds/demoplugin/uploaders/demo.go @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package uploaders + +import ( + "io" + "os" + "path/filepath" + + "github.com/open-component-model/ocm/pkg/contexts/credentials" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" + + "github.com/open-component-model/ocm/cmds/demoplugin/accessmethods" +) + +type Uploader struct { + ppi.UploaderBase +} + +var _ ppi.Uploader = (*Uploader)(nil) + +func New() ppi.Uploader { + return &Uploader{ + UploaderBase: ppi.MustNewUploaderBase("demo", "upload temp files"), + } +} + +func (a *Uploader) Writer(p ppi.Plugin, arttype, mediatype, hint string, creds credentials.Credentials) (io.WriteCloser, ppi.AccessSpecProvider, error) { + var file *os.File + var err error + if hint == "" { + file, err = os.CreateTemp(os.TempDir(), "demo.*.blob") + } else { + file, err = os.OpenFile(filepath.Join(os.TempDir(), hint), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o600) + } + if err != nil { + return nil, nil, err + } + writer := NewWriter(file, mediatype, hint == "", accessmethods.NAME, accessmethods.VERSION) + return writer, writer.Specification, nil +} diff --git a/cmds/demoplugin/accessmethods/writer.go b/cmds/demoplugin/uploaders/writer.go similarity index 67% rename from cmds/demoplugin/accessmethods/writer.go rename to cmds/demoplugin/uploaders/writer.go index fe46edb04..9aebcaf4a 100644 --- a/cmds/demoplugin/accessmethods/writer.go +++ b/cmds/demoplugin/uploaders/writer.go @@ -2,12 +2,13 @@ // // SPDX-License-Identifier: Apache-2.0 -package accessmethods +package uploaders import ( "os" "path/filepath" + "github.com/open-component-model/ocm/cmds/demoplugin/accessmethods" "github.com/open-component-model/ocm/pkg/common" "github.com/open-component-model/ocm/pkg/common/accessio" "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" @@ -20,16 +21,18 @@ type writer = accessio.DigestWriter type Writer struct { *writer file *os.File + rename bool name string version string media string - spec *AccessSpec + spec *accessmethods.AccessSpec } -func NewWriter(file *os.File, media, name, version string) *Writer { +func NewWriter(file *os.File, media string, rename bool, name, version string) *Writer { return &Writer{ writer: accessio.NewDefaultDigestWriter(file), file: file, + rename: rename, name: name, version: version, media: media, @@ -39,12 +42,15 @@ func NewWriter(file *os.File, media, name, version string) *Writer { func (w *Writer) Close() error { err := w.writer.Close() if err == nil { - n := filepath.Join(os.TempDir(), common.DigestToFileName(w.writer.Digest())) - err := os.Rename(w.file.Name(), n) - if err != nil { - return errors.Wrapf(err, "cannot rename %q to %q", w.file.Name(), n) + n := w.file.Name() + if w.rename { + n = filepath.Join(os.TempDir(), common.DigestToFileName(w.writer.Digest())) + err := os.Rename(w.file.Name(), n) + if err != nil { + return errors.Wrapf(err, "cannot rename %q to %q", w.file.Name(), n) + } } - w.spec = &AccessSpec{ + w.spec = &accessmethods.AccessSpec{ ObjectVersionedType: runtime.NewVersionedObjectType(w.name, w.version), Path: n, MediaType: w.media, diff --git a/cmds/ocm/app/app.go b/cmds/ocm/app/app.go index fc5f60935..8147bfebb 100644 --- a/cmds/ocm/app/app.go +++ b/cmds/ocm/app/app.go @@ -27,6 +27,7 @@ import ( common2 "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common" "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/componentarchive" "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/components" + "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/plugins" "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/references" "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/resources" "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/sources" @@ -173,6 +174,7 @@ func NewCliCommand(ctx clictx.Context, mod ...func(clictx.Context, *cobra.Comman cmd.AddCommand(cmdutils.HideCommand(references.NewCommand(opts.Context))) cmd.AddCommand(cmdutils.HideCommand(sources.NewCommand(opts.Context))) cmd.AddCommand(cmdutils.HideCommand(components.NewCommand(opts.Context))) + cmd.AddCommand(cmdutils.HideCommand(plugins.NewCommand(opts.Context))) cmd.AddCommand(cmdutils.HideCommand(cachecmds.NewCommand(opts.Context))) cmd.AddCommand(cmdutils.HideCommand(ocicmds.NewCommand(opts.Context))) diff --git a/cmds/ocm/commands/ocmcmds/cmd.go b/cmds/ocm/commands/ocmcmds/cmd.go index 1f676f286..e406e1feb 100644 --- a/cmds/ocm/commands/ocmcmds/cmd.go +++ b/cmds/ocm/commands/ocmcmds/cmd.go @@ -10,6 +10,7 @@ import ( "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/componentarchive" "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/components" "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/ctf" + "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/plugins" "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/references" "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/resourceconfig" "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/resources" @@ -35,6 +36,7 @@ func NewCommand(ctx clictx.Context) *cobra.Command { cmd.AddCommand(ctf.NewCommand(ctx)) cmd.AddCommand(componentarchive.NewCommand(ctx)) cmd.AddCommand(versions.NewCommand(ctx)) + cmd.AddCommand(plugins.NewCommand(ctx)) cmd.AddCommand(topicocmrefs.New(ctx)) return cmd diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/inputtype.go b/cmds/ocm/commands/ocmcmds/common/inputs/inputtype.go index 0e4928242..47582fa65 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/inputtype.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/inputtype.go @@ -401,8 +401,7 @@ func newGenericInputSpec(data []byte, unmarshaler runtime.Unmarshaler) (*Generic func Usage(scheme InputTypeScheme) string { s := ` The resource specification supports the following blob input types, specified -with the field type in the input field: -` +with the field type in the input field:` for _, t := range scheme.KnownTypeNames() { s = fmt.Sprintf("%s\n\n- Input type %s\n\n%s", s, t, utils.IndentLines(scheme.GetInputType(t).Usage(), " ")) } diff --git a/cmds/ocm/commands/ocmcmds/plugins/get/cmd.go b/cmds/ocm/commands/ocmcmds/plugins/get/cmd.go index 0f1d54161..8ca9bb2fc 100644 --- a/cmds/ocm/commands/ocmcmds/plugins/get/cmd.go +++ b/cmds/ocm/commands/ocmcmds/plugins/get/cmd.go @@ -44,10 +44,6 @@ func (o *Command) ForName(name string) *cobra.Command { return &cobra.Command{ Use: "[] {}", Short: "get plugins", - Long: ` -Get lists information for all plugins specified, if no plugin is specified -all registered ones are listed. -`, Example: ` $ ocm get plugins $ ocm get plugins demo -o yaml @@ -55,6 +51,13 @@ $ ocm get plugins demo -o yaml } } +func (o *Command) Long() string { + return ` +Get lists information for all plugins specified, if no plugin is specified +all registered ones are listed. +` +} + func (o *Command) Complete(args []string) error { o.Names = args return nil diff --git a/cmds/ocm/commands/ocmcmds/resourceconfig/add/cmd.go b/cmds/ocm/commands/ocmcmds/resourceconfig/add/cmd.go index ee6a8f132..88ceb2dbd 100644 --- a/cmds/ocm/commands/ocmcmds/resourceconfig/add/cmd.go +++ b/cmds/ocm/commands/ocmcmds/resourceconfig/add/cmd.go @@ -48,7 +48,14 @@ func (o *Command) ForName(name string) *cobra.Command { Use: "[] { | =}", Args: cobra.MinimumNArgs(1), Short: "add a resource specification to a resource config file", - Long: ` + Example: ` +$ ocm add resource-config resources.yaml --name myresource --type PlainText --input '{ "type": "file", "path": "testdata/testcontent", "mediaType": "text/plain" }' +`, + } +} + +func (o *Command) Long() string { + return ` Add a resource specification to a resource config file used by ocm add resources. ` + o.Adder.Description() + ` Elements must follow the resource meta data description scheme of the component descriptor. @@ -61,12 +68,8 @@ This command accepts additional resource specification files describing the sour to add to a component version. ` + (&template.Options{}).Usage() + - inputs.Usage(inputs.DefaultInputTypeScheme) + - ocm.AccessUsage(o.OCMContext().AccessMethods(), true), - Example: ` -$ ocm add resource-config resources.yaml --name myresource --type PlainText --input '{ "type": "file", "path": "testdata/testcontent", "mediaType": "text/plain" }' -`, - } + inputs.Usage(inputs.DefaultInputTypeScheme) + + ocm.AccessUsage(o.OCMContext().AccessMethods(), true) } func (o *Command) Run() error { diff --git a/cmds/ocm/commands/ocmcmds/resources/add/cmd.go b/cmds/ocm/commands/ocmcmds/resources/add/cmd.go index 1c18dbb51..87bdb06e8 100644 --- a/cmds/ocm/commands/ocmcmds/resources/add/cmd.go +++ b/cmds/ocm/commands/ocmcmds/resources/add/cmd.go @@ -44,16 +44,6 @@ func (o *Command) ForName(name string) *cobra.Command { Use: "[] { | =}", Args: cobra.MinimumNArgs(1), Short: "add resources to a component version", - Long: ` -Add resources specified in a resource file to a component version. -So far only component archives are supported as target. - -This command accepts resource specification files describing the resources -to add to a component version. Elements must follow the resource meta data -description scheme of the component descriptor. -` + o.Adder.Description() + (&template.Options{}).Usage() + - inputs.Usage(inputs.DefaultInputTypeScheme) + - ocm.AccessUsage(o.OCMContext().AccessMethods(), true), Example: ` Add a resource directly by options
@@ -80,6 +70,19 @@ $ ocm add resources  path/to/ca  resources.yaml VERSION=1.0.0
 	}
 }
 
+func (o *Command) Long() string {
+	return `
+Add resources specified in a resource file to a component version.
+So far only component archives are supported as target.
+
+This command accepts  resource specification files describing the resources
+to add to a component version. Elements must follow the resource meta data
+description scheme of the component descriptor.
+` + o.Adder.Description() + (&template.Options{}).Usage() +
+		inputs.Usage(inputs.DefaultInputTypeScheme) +
+		ocm.AccessUsage(o.OCMContext().AccessMethods(), true)
+}
+
 func (o *Command) Run() error {
 	return o.ProcessResourceDescriptions("resources", ResourceSpecHandler{})
 }
diff --git a/cmds/ocm/commands/ocmcmds/resources/add/provider.go b/cmds/ocm/commands/ocmcmds/resources/add/provider.go
index 205d62d2e..5edbbf77e 100644
--- a/cmds/ocm/commands/ocmcmds/resources/add/provider.go
+++ b/cmds/ocm/commands/ocmcmds/resources/add/provider.go
@@ -32,5 +32,5 @@ func (p *ResourceSpecificationsProvider) addMeta(opts flagsets.ConfigOptions, co
 
 func (p *ResourceSpecificationsProvider) Description() string {
 	d := p.ContentResourceSpecificationsProvider.Description()
-	return d + "Non-local resources can be indicated using the option --external."
+	return d + "Non-local resources can be indicated using the option --external.\n"
 }
diff --git a/cmds/ocm/commands/ocmcmds/sourceconfig/add/cmd.go b/cmds/ocm/commands/ocmcmds/sourceconfig/add/cmd.go
index 56d1a947e..f59885266 100644
--- a/cmds/ocm/commands/ocmcmds/sourceconfig/add/cmd.go
+++ b/cmds/ocm/commands/ocmcmds/sourceconfig/add/cmd.go
@@ -49,7 +49,14 @@ func (o *Command) ForName(name string) *cobra.Command {
 		Use:   "[]  { | =}",
 		Args:  cobra.MinimumNArgs(1),
 		Short: "add a source specification to a source config file",
-		Long: `
+		Example: `
+$ ocm add source-config sources.yaml --name sources --type filesystem --access '{ "type": "gitHub", "repoUrl": "github.com/open-component-model/ocm", "commit": "xyz" }'
+`,
+	}
+}
+
+func (o *Command) Long() string {
+	return `
 Add a source specification to a source config file used by ocm add sources.
 ` + o.Adder.Description() + ` Elements must follow the resource meta data
 description scheme of the component descriptor.
@@ -64,12 +71,8 @@ This command accepts additional source specification files describing the source
 to add to a component version.
 
 ` + (&template.Options{}).Usage() +
-			inputs.Usage(inputs.DefaultInputTypeScheme) +
-			ocm.AccessUsage(o.OCMContext().AccessMethods(), true),
-		Example: `
-$ ocm add source-config sources.yaml --name sources --type filesystem --access '{ "type": "gitHub", "repoUrl": "github.com/open-component-model/ocm", "commit": "xyz" }'
-`,
-	}
+		inputs.Usage(inputs.DefaultInputTypeScheme) +
+		ocm.AccessUsage(o.OCMContext().AccessMethods(), true)
 }
 
 func (o *Command) Run() error {
diff --git a/cmds/ocm/commands/ocmcmds/sources/add/cmd.go b/cmds/ocm/commands/ocmcmds/sources/add/cmd.go
index a15f82a39..c27b43e40 100644
--- a/cmds/ocm/commands/ocmcmds/sources/add/cmd.go
+++ b/cmds/ocm/commands/ocmcmds/sources/add/cmd.go
@@ -44,7 +44,11 @@ func (o *Command) ForName(name string) *cobra.Command {
 		Use:   "[]  { | =}",
 		Args:  cobra.MinimumNArgs(1),
 		Short: "add source information to a component version",
-		Long: `
+	}
+}
+
+func (o *Command) Long() string {
+	return `
 Add source information specified in a resource file to a component version.
 So far only component archives are supported as target.
 
@@ -52,9 +56,8 @@ This command accepts source specification files describing the sources
 to add to a component version. Elements must follow the source meta data
 description scheme of the component descriptor.
 ` + o.Adder.Description() + (&template.Options{}).Usage() +
-			inputs.Usage(inputs.DefaultInputTypeScheme) +
-			ocm.AccessUsage(o.OCMContext().AccessMethods(), true),
-	}
+		inputs.Usage(inputs.DefaultInputTypeScheme) +
+		ocm.AccessUsage(o.OCMContext().AccessMethods(), true)
 }
 
 func (o *Command) Run() error {
diff --git a/cmds/ocm/pkg/template/registry.go b/cmds/ocm/pkg/template/registry.go
index 2be6876d1..2d4872a2e 100644
--- a/cmds/ocm/pkg/template/registry.go
+++ b/cmds/ocm/pkg/template/registry.go
@@ -94,13 +94,17 @@ There are several templaters that can be selected by the --templater%s %s\n\n%s", s, t, title, utils.IndentLines(desc, "  "))
+			if strings.TrimSpace(desc) == "" {
+				s = fmt.Sprintf("%s- %s %s\n\n", s, t, title)
+			} else {
+				s = fmt.Sprintf("%s- %s %s\n\n%s", s, t, title, utils.IndentLines(desc, "  "))
+			}
 			if !strings.HasSuffix(s, "\n") {
 				s += "\n"
 			}
 		}
 	}
-	return s + "\n"
+	return s
 }
 
 var _registry = NewRegistry()
diff --git a/cmds/ocm/pkg/template/template.go b/cmds/ocm/pkg/template/template.go
index 5136495fe..2b66aa852 100644
--- a/cmds/ocm/pkg/template/template.go
+++ b/cmds/ocm/pkg/template/template.go
@@ -74,7 +74,6 @@ func (o *Options) Complete(fs vfs.FileSystem) error {
 // Usage prints out the usage for templating.
 func (o *Options) Usage() string {
 	return `
-Templating:
 All yaml/json defined resources can be templated.
 Variables are specified as regular arguments following the syntax <name>=<value>.
 Additionally settings can be specified by a yaml file using the --settings 
diff --git a/cmds/ocm/pkg/utils/command.go b/cmds/ocm/pkg/utils/command.go
index 13222c0b8..d6d5e82bd 100644
--- a/cmds/ocm/pkg/utils/command.go
+++ b/cmds/ocm/pkg/utils/command.go
@@ -5,6 +5,7 @@
 package utils
 
 import (
+	"reflect"
 	"strings"
 
 	"github.com/spf13/cobra"
@@ -33,6 +34,34 @@ type OCMCommand interface {
 	Run() error
 }
 
+////////////////////////////////////////////////////////////////////////////////
+// optional interfaces for a Command
+
+// Updater is a generic update function.
+type Updater interface {
+	Update(cmd *cobra.Command, args []string)
+}
+
+// Long is used to provide the long description by a method of the
+// command.
+type Long interface {
+	Long() string
+}
+
+// Short is used to provide the short description by a method of the
+// command.
+type Short interface {
+	Short() string
+}
+
+// Example is used to provide the example description by a method of the
+// command.
+type Example interface {
+	Example() string
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
 // BaseCommand provides the basic functionality of an OCM command
 // to carry a context and a set of reusable option specs.
 type BaseCommand struct {
@@ -81,13 +110,65 @@ func MassageCommand(cmd *cobra.Command, names ...string) *cobra.Command {
 	return cmd
 }
 
-// SetupCommand uses the OCMCommand to create and tweaks a cobra command
+// updateFunction composes an update function for field setter or a generic
+// update function used to update cobra command attributes based on optional
+// methods provided by the OCM command.
+// The generated update function is then called to update attributes of the
+// cobra command prior to help output.
+// If field setters are found, their values will override explicit settings
+// configured for the cobra command structure.
+// If no updaters are found, the statically configured values at the cobra
+// command struct will be used.
+func updateFunction[T any](f func(cmd *cobra.Command, args []string), source OCMCommand, target *cobra.Command) func(cmd *cobra.Command, args []string) {
+	var rf func(cmd *cobra.Command, args []string)
+
+	if t, ok := source.(T); ok {
+		var i *T
+		name := reflect.TypeOf(i).Elem().Method(0).Name
+		m := reflect.ValueOf(t).MethodByName(name)
+
+		tv := reflect.ValueOf(target).Elem()
+		if field := tv.FieldByName(name); field.IsValid() {
+			rf = func(cmd *cobra.Command, args []string) {
+				field.Set(m.Call([]reflect.Value{})[0])
+			}
+			rf(target, nil)
+		} else {
+			rf = func(cmd *cobra.Command, args []string) {
+				m.Call([]reflect.Value{reflect.ValueOf(cmd), reflect.ValueOf(args)})
+			}
+		}
+	}
+	if f == nil {
+		return rf
+	}
+	if rf == nil {
+		return f
+	}
+	return func(cmd *cobra.Command, args []string) {
+		f(cmd, args)
+		rf(cmd, args)
+	}
+}
+
+// SetupCommand uses the OCMCommand to create and tweak a cobra command
 // to incorporate the additional reusable option specs and their usage documentation.
 // Before the command executions the various Complete method flavors are
 // executed on the additional options ond the OCMCommand.
+// It also prepares the help system to reflect dynamic settings provided
+// by root command options by using a generated update function based
+// on optional methods of the OCM command.
 func SetupCommand(ocmcmd OCMCommand, names ...string) *cobra.Command {
 	c := ocmcmd.ForName(names[0])
 	MassageCommand(c, names...)
+
+	var update func(cmd *cobra.Command, args []string)
+
+	update = updateFunction[Long](update, ocmcmd, c)
+	update = updateFunction[Short](update, ocmcmd, c)
+	update = updateFunction[Example](update, ocmcmd, c)
+	update = updateFunction[Updater](update, ocmcmd, c)
+
 	c.RunE = func(cmd *cobra.Command, args []string) error {
 		var err error
 		if set, ok := ocmcmd.(options.OptionSetProvider); ok {
@@ -109,6 +190,33 @@ func SetupCommand(ocmcmd OCMCommand, names ...string) *cobra.Command {
 	if u, ok := ocmcmd.(options.Usage); ok {
 		c.Long += u.Usage()
 	}
+
+	help := c.HelpFunc()
+	if update != nil {
+		c.SetHelpFunc(func(cmd *cobra.Command, args []string) {
+			// assure root options are evaluated to provide appropriate base
+			// for update functions. PreRun functions will not be called
+			// if option --help is used to call the help function, so just
+			// call it in such a case.
+			for _, a := range args {
+				if a == "--help" {
+					root := cmd.Root()
+					if cmd != root {
+						if root.PersistentPreRunE != nil {
+							root.PersistentPreRunE(cmd, args)
+						} else {
+							if root.PersistentPreRun != nil {
+								root.PersistentPreRun(cmd, args)
+							}
+						}
+					}
+					break
+				}
+			}
+			update(cmd, args)
+			help(cmd, args)
+		})
+	}
 	ocmcmd.AddFlags(c.Flags())
 	return c
 }
diff --git a/docs/reference/ocm_add_references.md b/docs/reference/ocm_add_references.md
index d8dff644c..03bb7fa17 100644
--- a/docs/reference/ocm_add_references.md
+++ b/docs/reference/ocm_add_references.md
@@ -53,7 +53,6 @@ Therefore, basic references not requiring any additional labels or extra
 identities can just be specified by those simple value options without the need
 for the YAML option.
 
-Templating:
 All yaml/json defined resources can be templated.
 Variables are specified as regular arguments following the syntax <name>=<value>.
 Additionally settings can be specified by a yaml file using the --settings 
@@ -77,7 +76,6 @@ There are several templaters that can be selected by the --templaternone do not do any substitution.
 
-  
 - spiff [spiff templating](https://github.com/mandelsoft/spiff).
 
   It supports complex values. the settings are accessible using the binding values.
@@ -96,7 +94,6 @@ There are several templaters that can be selected by the --templater--access
 --input must be given. They take a YAML or JSON value describing an
 attribute set, also. The structure of those values is similar to the access
 or input fields of the description file format.
-Non-local resources can be indicated using the option --external. Elements must follow the resource meta data
+Non-local resources can be indicated using the option --external.
+ Elements must follow the resource meta data
 description scheme of the component descriptor.
 
 If expressions/templates are used in the specification file an appropriate
@@ -103,7 +104,6 @@ This command accepts additional resource specification files describing the sour
 to add to a component version.
 
 
-Templating:
 All yaml/json defined resources can be templated.
 Variables are specified as regular arguments following the syntax <name>=<value>.
 Additionally settings can be specified by a yaml file using the --settings 
@@ -127,7 +127,6 @@ There are several templaters that can be selected by the --templaternone do not do any substitution.
 
-  
 - spiff [spiff templating](https://github.com/mandelsoft/spiff).
 
   It supports complex values. the settings are accessible using the binding values.
@@ -145,11 +144,9 @@ There are several templaters that can be selected by the --templater
   
 
-
 The resource specification supports the following blob input types, specified
 with the field type in the input field:
 
-
 - Input type dir
 
   The path must denote a directory relative to the resources file, which is packed
@@ -204,7 +201,6 @@ with the field type in the input field:
   
   Options used to configure fields: --inputCompress, --inputExcludes, --inputFollowSymlinks, --inputIncludes, --inputPath, --inputPreserveDir, --mediaType
 
-
 - Input type docker
 
   The path must denote an image tag that can be found in the local
@@ -224,7 +220,6 @@ with the field type in the input field:
   
   Options used to configure fields: --hint, --inputPath
 
-
 - Input type dockermulti
 
   This input type describes the composition of a multi-platform OCI image.
@@ -246,7 +241,6 @@ with the field type in the input field:
   
   Options used to configure fields: --hint, --inputVariants
 
-
 - Input type file
 
   The path must denote a file relative the resources file. 
@@ -272,7 +266,6 @@ with the field type in the input field:
   
   Options used to configure fields: --inputCompress, --inputPath, --mediaType
 
-
 - Input type helm
 
   The path must denote an helm chart archive or directory
@@ -299,7 +292,6 @@ with the field type in the input field:
   
   Options used to configure fields: --inputCompress, --inputPath, --inputVersion, --mediaType
 
-
 - Input type ociImage
 
   The path must denote an OCI image reference.
@@ -318,7 +310,6 @@ with the field type in the input field:
   
   Options used to configure fields: --hint, --inputCompress, --inputPath, --mediaType
 
-
 - Input type spiff
 
   The path must denote a [spiff](https://github.com/mandelsoft/spiff) template relative the the resources file.
@@ -357,60 +348,59 @@ with the field type in the input field:
   
   Options used to configure fields: --inputCompress, --inputLibraries, --inputPath, --inputValues, --mediaType
 
-
 The following access methods are known by the system.
 Typically there is special support for the CLI artifact add commands.
 The following types (with the field type in the access field
 are handled:
 
-
-
 - Access type S3
 
   This method implements the access of a blob stored in an S3 bucket.
+
+  The following versions are supported:
+  - Version v1
   
-  Supported specification version is v1
-  
-  The type specific specification fields are:
-  
-  - **region** (optional) *string*
-  
-    OCI repository reference (this artefact name used to store the blob).
-  
-  - **bucket** *string*
-  
-    The name of the S3 bucket containing the blob
-  
-  - **key** *string*
-  
-    The key of the desired blob
+    The type specific specification fields are:
+    
+    - **region** (optional) *string*
+    
+      OCI repository reference (this artefact name used to store the blob).
+    
+    - **bucket** *string*
+    
+      The name of the S3 bucket containing the blob
+    
+    - **key** *string*
+    
+      The key of the desired blob
+    
+    Options used to configure fields: --accessVersion, --bucket, --mediaType, --reference, --region
   
-  Options used to configure fields: --accessVersion, --bucket, --mediaType, --reference, --region
-
 
 - Access type gitHub
 
   This method implements the access of the content of a git commit stored in a
   GitHub repository.
+
+  The following versions are supported:
+  - Version v1
   
-  Supported specification version is v1
-  
-  The type specific specification fields are:
-  
-  - **repoUrl**  *string*
-  
-    Repository URL with or without scheme.
-  
-  - **ref** (optional) *string*
-  
-    Original ref used to get the commit from
-  
-  - **commit** *string*
-  
-    The sha/id of the git commit
+    The type specific specification fields are:
+    
+    - **repoUrl**  *string*
+    
+      Repository URL with or without scheme.
+    
+    - **ref** (optional) *string*
+    
+      Original ref used to get the commit from
+    
+    - **commit** *string*
+    
+      The sha/id of the git commit
+    
+    Options used to configure fields: --accessHostname, --accessRepository, --commit
   
-  Options used to configure fields: --accessHostname, --accessRepository, --commit
-
 
 - Access type localBlob
 
@@ -424,93 +414,100 @@ are handled:
   
   Regardless of the chosen implementation the attribute specification is
   defined globally the same.
+
+  The following versions are supported:
+  - Version v1
   
-  Supported specification version is v1
-  
-  The type specific specification fields are:
-  
-  - **localReference** *string*
-  
-    Repository type specific location information as string. The value
-    may encode any deep structure, but typically just an access path is sufficient.
-  
-  - **mediaType** *string*
-  
-    The media type of the blob used to store the resource. It may add 
-    format information like +tar or +gzip.
-  
-  - **referenceName** (optional) *string*
-  
-    This optional attribute may contain identity information used by
-    other repositories to restore some global access with an identity
-    related to the original source.
-  
-    For example, if an OCI artefact originally referenced using the
-    access method [ociArtefact](../../../../../docs/formats/accessmethods/ociArtefact.md) is stored during
-    some transport step as local artefact, the reference name can be set
-    to its original repository name. An import step into an OCI based OCM
-    repository may then decide to make this artefact available again as 
-    regular OCI artefact.
-  
-  - **globalAccess** (optional) *access method specification*
-  
-    If a resource blob is stored locally, the repository implementation
-    may decide to provide an external access information (independent
-    of the OCM model).
-  
-    For example, an OCI artefact stored as local blob
-    can be additionally stored as regular OCI artefact in an OCI registry.
+    The type specific specification fields are:
+    
+    - **localReference** *string*
+    
+      Repository type specific location information as string. The value
+      may encode any deep structure, but typically just an access path is sufficient.
+    
+    - **mediaType** *string*
+    
+      The media type of the blob used to store the resource. It may add 
+      format information like +tar or +gzip.
+    
+    - **referenceName** (optional) *string*
+    
+      This optional attribute may contain identity information used by
+      other repositories to restore some global access with an identity
+      related to the original source.
     
-    This additional external access information can be added using
-    a second external access method specification.
+      For example, if an OCI artefact originally referenced using the
+      access method [ociArtefact](../../../../../docs/formats/accessmethods/ociArtefact.md) is stored during
+      some transport step as local artefact, the reference name can be set
+      to its original repository name. An import step into an OCI based OCM
+      repository may then decide to make this artefact available again as 
+      regular OCI artefact.
+    
+    - **globalAccess** (optional) *access method specification*
+    
+      If a resource blob is stored locally, the repository implementation
+      may decide to provide an external access information (independent
+      of the OCM model).
+    
+      For example, an OCI artefact stored as local blob
+      can be additionally stored as regular OCI artefact in an OCI registry.
+      
+      This additional external access information can be added using
+      a second external access method specification.
+    
+    Options used to configure fields: --globalAccess, --hint, --mediaType, --reference
   
-  Options used to configure fields: --globalAccess, --hint, --mediaType, --reference
+
+- Access type none
+
+  dummy resource with no access
 
 
 - Access type ociArtefact
 
   This method implements the access of an OCI artefact stored in an OCI registry.
+
+  The following versions are supported:
+  - Version v1
   
-  Supported specification version is v1
-  
-  The type specific specification fields are:
-  
-  - **imageReference** *string*
-  
-    OCI image/artefact reference following the possible docker schemes:
-    - <repo>/<artefact>:<digest>@<tag>
-    - [<port>]/<repo path>/<artefact>:<version>@<tag>
+    The type specific specification fields are:
+    
+    - **imageReference** *string*
+    
+      OCI image/artefact reference following the possible docker schemes:
+      - <repo>/<artefact>:<digest>@<tag>
+      - [<port>]/<repo path>/<artefact>:<version>@<tag>
+    
+    Options used to configure fields: --reference
   
-  Options used to configure fields: --reference
-
 
 - Access type ociBlob
 
   This method implements the access of an OCI blob stored in an OCI repository.
+
+  The following versions are supported:
+  - Version v1
   
-  Supported specification version is v1
-  
-  The type specific specification fields are:
-  
-  - **imageReference** *string*
-  
-    OCI repository reference (this artefact name used to store the blob).
-  
-  - **mediaType** *string*
-  
-    The media type of the blob
-  
-  - **digest** *string*
-  
-    The digest of the blob used to access the blob in the OCI repository.
-  
-  - **size** *integer*
-  
-    The size of the blob
-  
+    The type specific specification fields are:
+    
+    - **imageReference** *string*
+    
+      OCI repository reference (this artefact name used to store the blob).
+    
+    - **mediaType** *string*
+    
+      The media type of the blob
+    
+    - **digest** *string*
+    
+      The digest of the blob used to access the blob in the OCI repository.
+    
+    - **size** *integer*
+    
+      The size of the blob
+    
+    Options used to configure fields: --digest, --mediaType, --reference, --size
   
-  Options used to configure fields: --digest, --mediaType, --reference, --size
-
 
 
 ### Examples
diff --git a/docs/reference/ocm_add_resources.md b/docs/reference/ocm_add_resources.md
index 0b3a753e6..70fbdb1b6 100644
--- a/docs/reference/ocm_add_resources.md
+++ b/docs/reference/ocm_add_resources.md
@@ -98,7 +98,7 @@ To describe the content of this element one of the options --access
 attribute set, also. The structure of those values is similar to the access
 or input fields of the description file format.
 Non-local resources can be indicated using the option --external.
-Templating:
+
 All yaml/json defined resources can be templated.
 Variables are specified as regular arguments following the syntax <name>=<value>.
 Additionally settings can be specified by a yaml file using the --settings 
@@ -122,7 +122,6 @@ There are several templaters that can be selected by the --templaternone do not do any substitution.
 
-  
 - spiff [spiff templating](https://github.com/mandelsoft/spiff).
 
   It supports complex values. the settings are accessible using the binding values.
@@ -140,11 +139,9 @@ There are several templaters that can be selected by the --templater
   
 
-
 The resource specification supports the following blob input types, specified
 with the field type in the input field:
 
-
 - Input type dir
 
   The path must denote a directory relative to the resources file, which is packed
@@ -199,7 +196,6 @@ with the field type in the input field:
   
   Options used to configure fields: --inputCompress, --inputExcludes, --inputFollowSymlinks, --inputIncludes, --inputPath, --inputPreserveDir, --mediaType
 
-
 - Input type docker
 
   The path must denote an image tag that can be found in the local
@@ -219,7 +215,6 @@ with the field type in the input field:
   
   Options used to configure fields: --hint, --inputPath
 
-
 - Input type dockermulti
 
   This input type describes the composition of a multi-platform OCI image.
@@ -241,7 +236,6 @@ with the field type in the input field:
   
   Options used to configure fields: --hint, --inputVariants
 
-
 - Input type file
 
   The path must denote a file relative the resources file. 
@@ -267,7 +261,6 @@ with the field type in the input field:
   
   Options used to configure fields: --inputCompress, --inputPath, --mediaType
 
-
 - Input type helm
 
   The path must denote an helm chart archive or directory
@@ -294,7 +287,6 @@ with the field type in the input field:
   
   Options used to configure fields: --inputCompress, --inputPath, --inputVersion, --mediaType
 
-
 - Input type ociImage
 
   The path must denote an OCI image reference.
@@ -313,7 +305,6 @@ with the field type in the input field:
   
   Options used to configure fields: --hint, --inputCompress, --inputPath, --mediaType
 
-
 - Input type spiff
 
   The path must denote a [spiff](https://github.com/mandelsoft/spiff) template relative the the resources file.
@@ -352,60 +343,59 @@ with the field type in the input field:
   
   Options used to configure fields: --inputCompress, --inputLibraries, --inputPath, --inputValues, --mediaType
 
-
 The following access methods are known by the system.
 Typically there is special support for the CLI artifact add commands.
 The following types (with the field type in the access field
 are handled:
 
-
-
 - Access type S3
 
   This method implements the access of a blob stored in an S3 bucket.
+
+  The following versions are supported:
+  - Version v1
   
-  Supported specification version is v1
-  
-  The type specific specification fields are:
-  
-  - **region** (optional) *string*
-  
-    OCI repository reference (this artefact name used to store the blob).
-  
-  - **bucket** *string*
-  
-    The name of the S3 bucket containing the blob
-  
-  - **key** *string*
-  
-    The key of the desired blob
+    The type specific specification fields are:
+    
+    - **region** (optional) *string*
+    
+      OCI repository reference (this artefact name used to store the blob).
+    
+    - **bucket** *string*
+    
+      The name of the S3 bucket containing the blob
+    
+    - **key** *string*
+    
+      The key of the desired blob
+    
+    Options used to configure fields: --accessVersion, --bucket, --mediaType, --reference, --region
   
-  Options used to configure fields: --accessVersion, --bucket, --mediaType, --reference, --region
-
 
 - Access type gitHub
 
   This method implements the access of the content of a git commit stored in a
   GitHub repository.
+
+  The following versions are supported:
+  - Version v1
   
-  Supported specification version is v1
-  
-  The type specific specification fields are:
-  
-  - **repoUrl**  *string*
-  
-    Repository URL with or without scheme.
-  
-  - **ref** (optional) *string*
-  
-    Original ref used to get the commit from
-  
-  - **commit** *string*
-  
-    The sha/id of the git commit
+    The type specific specification fields are:
+    
+    - **repoUrl**  *string*
+    
+      Repository URL with or without scheme.
+    
+    - **ref** (optional) *string*
+    
+      Original ref used to get the commit from
+    
+    - **commit** *string*
+    
+      The sha/id of the git commit
+    
+    Options used to configure fields: --accessHostname, --accessRepository, --commit
   
-  Options used to configure fields: --accessHostname, --accessRepository, --commit
-
 
 - Access type localBlob
 
@@ -419,93 +409,100 @@ are handled:
   
   Regardless of the chosen implementation the attribute specification is
   defined globally the same.
+
+  The following versions are supported:
+  - Version v1
   
-  Supported specification version is v1
-  
-  The type specific specification fields are:
-  
-  - **localReference** *string*
-  
-    Repository type specific location information as string. The value
-    may encode any deep structure, but typically just an access path is sufficient.
-  
-  - **mediaType** *string*
-  
-    The media type of the blob used to store the resource. It may add 
-    format information like +tar or +gzip.
-  
-  - **referenceName** (optional) *string*
-  
-    This optional attribute may contain identity information used by
-    other repositories to restore some global access with an identity
-    related to the original source.
-  
-    For example, if an OCI artefact originally referenced using the
-    access method [ociArtefact](../../../../../docs/formats/accessmethods/ociArtefact.md) is stored during
-    some transport step as local artefact, the reference name can be set
-    to its original repository name. An import step into an OCI based OCM
-    repository may then decide to make this artefact available again as 
-    regular OCI artefact.
-  
-  - **globalAccess** (optional) *access method specification*
-  
-    If a resource blob is stored locally, the repository implementation
-    may decide to provide an external access information (independent
-    of the OCM model).
-  
-    For example, an OCI artefact stored as local blob
-    can be additionally stored as regular OCI artefact in an OCI registry.
+    The type specific specification fields are:
+    
+    - **localReference** *string*
+    
+      Repository type specific location information as string. The value
+      may encode any deep structure, but typically just an access path is sufficient.
+    
+    - **mediaType** *string*
+    
+      The media type of the blob used to store the resource. It may add 
+      format information like +tar or +gzip.
+    
+    - **referenceName** (optional) *string*
     
-    This additional external access information can be added using
-    a second external access method specification.
+      This optional attribute may contain identity information used by
+      other repositories to restore some global access with an identity
+      related to the original source.
+    
+      For example, if an OCI artefact originally referenced using the
+      access method [ociArtefact](../../../../../docs/formats/accessmethods/ociArtefact.md) is stored during
+      some transport step as local artefact, the reference name can be set
+      to its original repository name. An import step into an OCI based OCM
+      repository may then decide to make this artefact available again as 
+      regular OCI artefact.
+    
+    - **globalAccess** (optional) *access method specification*
+    
+      If a resource blob is stored locally, the repository implementation
+      may decide to provide an external access information (independent
+      of the OCM model).
+    
+      For example, an OCI artefact stored as local blob
+      can be additionally stored as regular OCI artefact in an OCI registry.
+      
+      This additional external access information can be added using
+      a second external access method specification.
+    
+    Options used to configure fields: --globalAccess, --hint, --mediaType, --reference
   
-  Options used to configure fields: --globalAccess, --hint, --mediaType, --reference
+
+- Access type none
+
+  dummy resource with no access
 
 
 - Access type ociArtefact
 
   This method implements the access of an OCI artefact stored in an OCI registry.
+
+  The following versions are supported:
+  - Version v1
   
-  Supported specification version is v1
-  
-  The type specific specification fields are:
-  
-  - **imageReference** *string*
-  
-    OCI image/artefact reference following the possible docker schemes:
-    - <repo>/<artefact>:<digest>@<tag>
-    - [<port>]/<repo path>/<artefact>:<version>@<tag>
+    The type specific specification fields are:
+    
+    - **imageReference** *string*
+    
+      OCI image/artefact reference following the possible docker schemes:
+      - <repo>/<artefact>:<digest>@<tag>
+      - [<port>]/<repo path>/<artefact>:<version>@<tag>
+    
+    Options used to configure fields: --reference
   
-  Options used to configure fields: --reference
-
 
 - Access type ociBlob
 
   This method implements the access of an OCI blob stored in an OCI repository.
+
+  The following versions are supported:
+  - Version v1
   
-  Supported specification version is v1
-  
-  The type specific specification fields are:
-  
-  - **imageReference** *string*
-  
-    OCI repository reference (this artefact name used to store the blob).
-  
-  - **mediaType** *string*
-  
-    The media type of the blob
-  
-  - **digest** *string*
-  
-    The digest of the blob used to access the blob in the OCI repository.
-  
-  - **size** *integer*
-  
-    The size of the blob
-  
+    The type specific specification fields are:
+    
+    - **imageReference** *string*
+    
+      OCI repository reference (this artefact name used to store the blob).
+    
+    - **mediaType** *string*
+    
+      The media type of the blob
+    
+    - **digest** *string*
+    
+      The digest of the blob used to access the blob in the OCI repository.
+    
+    - **size** *integer*
+    
+      The size of the blob
+    
+    Options used to configure fields: --digest, --mediaType, --reference, --size
   
-  Options used to configure fields: --digest, --mediaType, --reference, --size
-
 
 
 ### Examples
diff --git a/docs/reference/ocm_add_source-configuration.md b/docs/reference/ocm_add_source-configuration.md
index 849dfeccc..a726fd134 100644
--- a/docs/reference/ocm_add_source-configuration.md
+++ b/docs/reference/ocm_add_source-configuration.md
@@ -104,7 +104,6 @@ This command accepts additional source specification files describing the source
 to add to a component version.
 
 
-Templating:
 All yaml/json defined resources can be templated.
 Variables are specified as regular arguments following the syntax <name>=<value>.
 Additionally settings can be specified by a yaml file using the --settings 
@@ -128,7 +127,6 @@ There are several templaters that can be selected by the --templaternone do not do any substitution.
 
-  
 - spiff [spiff templating](https://github.com/mandelsoft/spiff).
 
   It supports complex values. the settings are accessible using the binding values.
@@ -146,11 +144,9 @@ There are several templaters that can be selected by the --templater
   
 
-
 The resource specification supports the following blob input types, specified
 with the field type in the input field:
 
-
 - Input type dir
 
   The path must denote a directory relative to the resources file, which is packed
@@ -205,7 +201,6 @@ with the field type in the input field:
   
   Options used to configure fields: --inputCompress, --inputExcludes, --inputFollowSymlinks, --inputIncludes, --inputPath, --inputPreserveDir, --mediaType
 
-
 - Input type docker
 
   The path must denote an image tag that can be found in the local
@@ -225,7 +220,6 @@ with the field type in the input field:
   
   Options used to configure fields: --hint, --inputPath
 
-
 - Input type dockermulti
 
   This input type describes the composition of a multi-platform OCI image.
@@ -247,7 +241,6 @@ with the field type in the input field:
   
   Options used to configure fields: --hint, --inputVariants
 
-
 - Input type file
 
   The path must denote a file relative the resources file. 
@@ -273,7 +266,6 @@ with the field type in the input field:
   
   Options used to configure fields: --inputCompress, --inputPath, --mediaType
 
-
 - Input type helm
 
   The path must denote an helm chart archive or directory
@@ -300,7 +292,6 @@ with the field type in the input field:
   
   Options used to configure fields: --inputCompress, --inputPath, --inputVersion, --mediaType
 
-
 - Input type ociImage
 
   The path must denote an OCI image reference.
@@ -319,7 +310,6 @@ with the field type in the input field:
   
   Options used to configure fields: --hint, --inputCompress, --inputPath, --mediaType
 
-
 - Input type spiff
 
   The path must denote a [spiff](https://github.com/mandelsoft/spiff) template relative the the resources file.
@@ -358,60 +348,59 @@ with the field type in the input field:
   
   Options used to configure fields: --inputCompress, --inputLibraries, --inputPath, --inputValues, --mediaType
 
-
 The following access methods are known by the system.
 Typically there is special support for the CLI artifact add commands.
 The following types (with the field type in the access field
 are handled:
 
-
-
 - Access type S3
 
   This method implements the access of a blob stored in an S3 bucket.
+
+  The following versions are supported:
+  - Version v1
   
-  Supported specification version is v1
-  
-  The type specific specification fields are:
-  
-  - **region** (optional) *string*
-  
-    OCI repository reference (this artefact name used to store the blob).
-  
-  - **bucket** *string*
-  
-    The name of the S3 bucket containing the blob
-  
-  - **key** *string*
-  
-    The key of the desired blob
+    The type specific specification fields are:
+    
+    - **region** (optional) *string*
+    
+      OCI repository reference (this artefact name used to store the blob).
+    
+    - **bucket** *string*
+    
+      The name of the S3 bucket containing the blob
+    
+    - **key** *string*
+    
+      The key of the desired blob
+    
+    Options used to configure fields: --accessVersion, --bucket, --mediaType, --reference, --region
   
-  Options used to configure fields: --accessVersion, --bucket, --mediaType, --reference, --region
-
 
 - Access type gitHub
 
   This method implements the access of the content of a git commit stored in a
   GitHub repository.
+
+  The following versions are supported:
+  - Version v1
   
-  Supported specification version is v1
-  
-  The type specific specification fields are:
-  
-  - **repoUrl**  *string*
-  
-    Repository URL with or without scheme.
-  
-  - **ref** (optional) *string*
-  
-    Original ref used to get the commit from
-  
-  - **commit** *string*
-  
-    The sha/id of the git commit
+    The type specific specification fields are:
+    
+    - **repoUrl**  *string*
+    
+      Repository URL with or without scheme.
+    
+    - **ref** (optional) *string*
+    
+      Original ref used to get the commit from
+    
+    - **commit** *string*
+    
+      The sha/id of the git commit
+    
+    Options used to configure fields: --accessHostname, --accessRepository, --commit
   
-  Options used to configure fields: --accessHostname, --accessRepository, --commit
-
 
 - Access type localBlob
 
@@ -425,93 +414,100 @@ are handled:
   
   Regardless of the chosen implementation the attribute specification is
   defined globally the same.
+
+  The following versions are supported:
+  - Version v1
   
-  Supported specification version is v1
-  
-  The type specific specification fields are:
-  
-  - **localReference** *string*
-  
-    Repository type specific location information as string. The value
-    may encode any deep structure, but typically just an access path is sufficient.
-  
-  - **mediaType** *string*
-  
-    The media type of the blob used to store the resource. It may add 
-    format information like +tar or +gzip.
-  
-  - **referenceName** (optional) *string*
-  
-    This optional attribute may contain identity information used by
-    other repositories to restore some global access with an identity
-    related to the original source.
-  
-    For example, if an OCI artefact originally referenced using the
-    access method [ociArtefact](../../../../../docs/formats/accessmethods/ociArtefact.md) is stored during
-    some transport step as local artefact, the reference name can be set
-    to its original repository name. An import step into an OCI based OCM
-    repository may then decide to make this artefact available again as 
-    regular OCI artefact.
-  
-  - **globalAccess** (optional) *access method specification*
-  
-    If a resource blob is stored locally, the repository implementation
-    may decide to provide an external access information (independent
-    of the OCM model).
-  
-    For example, an OCI artefact stored as local blob
-    can be additionally stored as regular OCI artefact in an OCI registry.
+    The type specific specification fields are:
+    
+    - **localReference** *string*
+    
+      Repository type specific location information as string. The value
+      may encode any deep structure, but typically just an access path is sufficient.
+    
+    - **mediaType** *string*
+    
+      The media type of the blob used to store the resource. It may add 
+      format information like +tar or +gzip.
+    
+    - **referenceName** (optional) *string*
+    
+      This optional attribute may contain identity information used by
+      other repositories to restore some global access with an identity
+      related to the original source.
     
-    This additional external access information can be added using
-    a second external access method specification.
+      For example, if an OCI artefact originally referenced using the
+      access method [ociArtefact](../../../../../docs/formats/accessmethods/ociArtefact.md) is stored during
+      some transport step as local artefact, the reference name can be set
+      to its original repository name. An import step into an OCI based OCM
+      repository may then decide to make this artefact available again as 
+      regular OCI artefact.
+    
+    - **globalAccess** (optional) *access method specification*
+    
+      If a resource blob is stored locally, the repository implementation
+      may decide to provide an external access information (independent
+      of the OCM model).
+    
+      For example, an OCI artefact stored as local blob
+      can be additionally stored as regular OCI artefact in an OCI registry.
+      
+      This additional external access information can be added using
+      a second external access method specification.
+    
+    Options used to configure fields: --globalAccess, --hint, --mediaType, --reference
   
-  Options used to configure fields: --globalAccess, --hint, --mediaType, --reference
+
+- Access type none
+
+  dummy resource with no access
 
 
 - Access type ociArtefact
 
   This method implements the access of an OCI artefact stored in an OCI registry.
+
+  The following versions are supported:
+  - Version v1
   
-  Supported specification version is v1
-  
-  The type specific specification fields are:
-  
-  - **imageReference** *string*
-  
-    OCI image/artefact reference following the possible docker schemes:
-    - <repo>/<artefact>:<digest>@<tag>
-    - [<port>]/<repo path>/<artefact>:<version>@<tag>
+    The type specific specification fields are:
+    
+    - **imageReference** *string*
+    
+      OCI image/artefact reference following the possible docker schemes:
+      - <repo>/<artefact>:<digest>@<tag>
+      - [<port>]/<repo path>/<artefact>:<version>@<tag>
+    
+    Options used to configure fields: --reference
   
-  Options used to configure fields: --reference
-
 
 - Access type ociBlob
 
   This method implements the access of an OCI blob stored in an OCI repository.
+
+  The following versions are supported:
+  - Version v1
   
-  Supported specification version is v1
-  
-  The type specific specification fields are:
-  
-  - **imageReference** *string*
-  
-    OCI repository reference (this artefact name used to store the blob).
-  
-  - **mediaType** *string*
-  
-    The media type of the blob
-  
-  - **digest** *string*
-  
-    The digest of the blob used to access the blob in the OCI repository.
-  
-  - **size** *integer*
-  
-    The size of the blob
-  
+    The type specific specification fields are:
+    
+    - **imageReference** *string*
+    
+      OCI repository reference (this artefact name used to store the blob).
+    
+    - **mediaType** *string*
+    
+      The media type of the blob
+    
+    - **digest** *string*
+    
+      The digest of the blob used to access the blob in the OCI repository.
+    
+    - **size** *integer*
+    
+      The size of the blob
+    
+    Options used to configure fields: --digest, --mediaType, --reference, --size
   
-  Options used to configure fields: --digest, --mediaType, --reference, --size
-
 
 
 ### Examples
diff --git a/docs/reference/ocm_add_sources.md b/docs/reference/ocm_add_sources.md
index d95338a66..1e91cd70e 100644
--- a/docs/reference/ocm_add_sources.md
+++ b/docs/reference/ocm_add_sources.md
@@ -97,7 +97,6 @@ To describe the content of this element one of the options --access
 attribute set, also. The structure of those values is similar to the access
 or input fields of the description file format.
 
-Templating:
 All yaml/json defined resources can be templated.
 Variables are specified as regular arguments following the syntax <name>=<value>.
 Additionally settings can be specified by a yaml file using the --settings 
@@ -121,7 +120,6 @@ There are several templaters that can be selected by the --templaternone do not do any substitution.
 
-  
 - spiff [spiff templating](https://github.com/mandelsoft/spiff).
 
   It supports complex values. the settings are accessible using the binding values.
@@ -139,11 +137,9 @@ There are several templaters that can be selected by the --templater
   
 
-
 The resource specification supports the following blob input types, specified
 with the field type in the input field:
 
-
 - Input type dir
 
   The path must denote a directory relative to the resources file, which is packed
@@ -198,7 +194,6 @@ with the field type in the input field:
   
   Options used to configure fields: --inputCompress, --inputExcludes, --inputFollowSymlinks, --inputIncludes, --inputPath, --inputPreserveDir, --mediaType
 
-
 - Input type docker
 
   The path must denote an image tag that can be found in the local
@@ -218,7 +213,6 @@ with the field type in the input field:
   
   Options used to configure fields: --hint, --inputPath
 
-
 - Input type dockermulti
 
   This input type describes the composition of a multi-platform OCI image.
@@ -240,7 +234,6 @@ with the field type in the input field:
   
   Options used to configure fields: --hint, --inputVariants
 
-
 - Input type file
 
   The path must denote a file relative the resources file. 
@@ -266,7 +259,6 @@ with the field type in the input field:
   
   Options used to configure fields: --inputCompress, --inputPath, --mediaType
 
-
 - Input type helm
 
   The path must denote an helm chart archive or directory
@@ -293,7 +285,6 @@ with the field type in the input field:
   
   Options used to configure fields: --inputCompress, --inputPath, --inputVersion, --mediaType
 
-
 - Input type ociImage
 
   The path must denote an OCI image reference.
@@ -312,7 +303,6 @@ with the field type in the input field:
   
   Options used to configure fields: --hint, --inputCompress, --inputPath, --mediaType
 
-
 - Input type spiff
 
   The path must denote a [spiff](https://github.com/mandelsoft/spiff) template relative the the resources file.
@@ -351,60 +341,59 @@ with the field type in the input field:
   
   Options used to configure fields: --inputCompress, --inputLibraries, --inputPath, --inputValues, --mediaType
 
-
 The following access methods are known by the system.
 Typically there is special support for the CLI artifact add commands.
 The following types (with the field type in the access field
 are handled:
 
-
-
 - Access type S3
 
   This method implements the access of a blob stored in an S3 bucket.
+
+  The following versions are supported:
+  - Version v1
   
-  Supported specification version is v1
-  
-  The type specific specification fields are:
-  
-  - **region** (optional) *string*
-  
-    OCI repository reference (this artefact name used to store the blob).
-  
-  - **bucket** *string*
-  
-    The name of the S3 bucket containing the blob
-  
-  - **key** *string*
-  
-    The key of the desired blob
+    The type specific specification fields are:
+    
+    - **region** (optional) *string*
+    
+      OCI repository reference (this artefact name used to store the blob).
+    
+    - **bucket** *string*
+    
+      The name of the S3 bucket containing the blob
+    
+    - **key** *string*
+    
+      The key of the desired blob
+    
+    Options used to configure fields: --accessVersion, --bucket, --mediaType, --reference, --region
   
-  Options used to configure fields: --accessVersion, --bucket, --mediaType, --reference, --region
-
 
 - Access type gitHub
 
   This method implements the access of the content of a git commit stored in a
   GitHub repository.
+
+  The following versions are supported:
+  - Version v1
   
-  Supported specification version is v1
-  
-  The type specific specification fields are:
-  
-  - **repoUrl**  *string*
-  
-    Repository URL with or without scheme.
-  
-  - **ref** (optional) *string*
-  
-    Original ref used to get the commit from
-  
-  - **commit** *string*
-  
-    The sha/id of the git commit
+    The type specific specification fields are:
+    
+    - **repoUrl**  *string*
+    
+      Repository URL with or without scheme.
+    
+    - **ref** (optional) *string*
+    
+      Original ref used to get the commit from
+    
+    - **commit** *string*
+    
+      The sha/id of the git commit
+    
+    Options used to configure fields: --accessHostname, --accessRepository, --commit
   
-  Options used to configure fields: --accessHostname, --accessRepository, --commit
-
 
 - Access type localBlob
 
@@ -418,93 +407,100 @@ are handled:
   
   Regardless of the chosen implementation the attribute specification is
   defined globally the same.
+
+  The following versions are supported:
+  - Version v1
   
-  Supported specification version is v1
-  
-  The type specific specification fields are:
-  
-  - **localReference** *string*
-  
-    Repository type specific location information as string. The value
-    may encode any deep structure, but typically just an access path is sufficient.
-  
-  - **mediaType** *string*
-  
-    The media type of the blob used to store the resource. It may add 
-    format information like +tar or +gzip.
-  
-  - **referenceName** (optional) *string*
-  
-    This optional attribute may contain identity information used by
-    other repositories to restore some global access with an identity
-    related to the original source.
-  
-    For example, if an OCI artefact originally referenced using the
-    access method [ociArtefact](../../../../../docs/formats/accessmethods/ociArtefact.md) is stored during
-    some transport step as local artefact, the reference name can be set
-    to its original repository name. An import step into an OCI based OCM
-    repository may then decide to make this artefact available again as 
-    regular OCI artefact.
-  
-  - **globalAccess** (optional) *access method specification*
-  
-    If a resource blob is stored locally, the repository implementation
-    may decide to provide an external access information (independent
-    of the OCM model).
-  
-    For example, an OCI artefact stored as local blob
-    can be additionally stored as regular OCI artefact in an OCI registry.
+    The type specific specification fields are:
+    
+    - **localReference** *string*
+    
+      Repository type specific location information as string. The value
+      may encode any deep structure, but typically just an access path is sufficient.
+    
+    - **mediaType** *string*
+    
+      The media type of the blob used to store the resource. It may add 
+      format information like +tar or +gzip.
+    
+    - **referenceName** (optional) *string*
+    
+      This optional attribute may contain identity information used by
+      other repositories to restore some global access with an identity
+      related to the original source.
     
-    This additional external access information can be added using
-    a second external access method specification.
+      For example, if an OCI artefact originally referenced using the
+      access method [ociArtefact](../../../../../docs/formats/accessmethods/ociArtefact.md) is stored during
+      some transport step as local artefact, the reference name can be set
+      to its original repository name. An import step into an OCI based OCM
+      repository may then decide to make this artefact available again as 
+      regular OCI artefact.
+    
+    - **globalAccess** (optional) *access method specification*
+    
+      If a resource blob is stored locally, the repository implementation
+      may decide to provide an external access information (independent
+      of the OCM model).
+    
+      For example, an OCI artefact stored as local blob
+      can be additionally stored as regular OCI artefact in an OCI registry.
+      
+      This additional external access information can be added using
+      a second external access method specification.
+    
+    Options used to configure fields: --globalAccess, --hint, --mediaType, --reference
   
-  Options used to configure fields: --globalAccess, --hint, --mediaType, --reference
+
+- Access type none
+
+  dummy resource with no access
 
 
 - Access type ociArtefact
 
   This method implements the access of an OCI artefact stored in an OCI registry.
+
+  The following versions are supported:
+  - Version v1
   
-  Supported specification version is v1
-  
-  The type specific specification fields are:
-  
-  - **imageReference** *string*
-  
-    OCI image/artefact reference following the possible docker schemes:
-    - <repo>/<artefact>:<digest>@<tag>
-    - [<port>]/<repo path>/<artefact>:<version>@<tag>
+    The type specific specification fields are:
+    
+    - **imageReference** *string*
+    
+      OCI image/artefact reference following the possible docker schemes:
+      - <repo>/<artefact>:<digest>@<tag>
+      - [<port>]/<repo path>/<artefact>:<version>@<tag>
+    
+    Options used to configure fields: --reference
   
-  Options used to configure fields: --reference
-
 
 - Access type ociBlob
 
   This method implements the access of an OCI blob stored in an OCI repository.
+
+  The following versions are supported:
+  - Version v1
   
-  Supported specification version is v1
-  
-  The type specific specification fields are:
-  
-  - **imageReference** *string*
-  
-    OCI repository reference (this artefact name used to store the blob).
-  
-  - **mediaType** *string*
-  
-    The media type of the blob
-  
-  - **digest** *string*
-  
-    The digest of the blob used to access the blob in the OCI repository.
-  
-  - **size** *integer*
-  
-    The size of the blob
-  
+    The type specific specification fields are:
+    
+    - **imageReference** *string*
+    
+      OCI repository reference (this artefact name used to store the blob).
+    
+    - **mediaType** *string*
+    
+      The media type of the blob
+    
+    - **digest** *string*
+    
+      The digest of the blob used to access the blob in the OCI repository.
+    
+    - **size** *integer*
+    
+      The size of the blob
+    
+    Options used to configure fields: --digest, --mediaType, --reference, --size
   
-  Options used to configure fields: --digest, --mediaType, --reference, --size
-
 
 
 ### SEE ALSO
diff --git a/docs/reference/ocm_configfile.md b/docs/reference/ocm_configfile.md
index b19ff723f..ca046491e 100644
--- a/docs/reference/ocm_configfile.md
+++ b/docs/reference/ocm_configfile.md
@@ -25,7 +25,6 @@ The following configuration types are supported:
          <name>: <yaml defining the attribute>
          ...
   
- - credentials.config.ocm.software The config type credentials.config.ocm.software can be used to define a list of arbitrary configuration specifications: @@ -51,7 +50,6 @@ The following configuration types are supported: - <credential specification> ... credential chain - - generic.config.ocm.software The config type generic.config.ocm.software can be used to define a list of arbitrary configuration specifications: @@ -63,7 +61,6 @@ The following configuration types are supported: ... ... - - keys.config.ocm.software The config type keys.config.ocm.software can be used to define public and private keys. A key value might be given by one of the fields: @@ -82,7 +79,6 @@ The following configuration types are supported: data: <base64 encoded key representation> ... - - logging.config.ocm.software The config type logging.config.ocm.software can be used to configure the logging aspect of a dedicated context type: @@ -101,7 +97,6 @@ The following configuration types are supported: If no context type is specified, the config will be applies to any target acting as logging context provider, which is not a non-root context. - - memory.credentials.config.ocm.software The config type memory.credentials.config.ocm.software can be used to define a list of arbitrary credentials stored in a memory based credentials repository: @@ -121,7 +116,6 @@ The following configuration types are supported: username: mandelsoft2 password: specialsecret2 - - oci.config.ocm.software The config type oci.config.ocm.software can be used to define OCI registry aliases: @@ -132,7 +126,6 @@ The following configuration types are supported: <name>: <OCI registry specification> ... - - ocm.cmd.config.ocm.software The config type ocm.cmd.config.ocm.software can be used to configure predefined aliases for dedicated OCM repositories and @@ -147,7 +140,6 @@ The following configuration types are supported: <name>: <specification of OCI registry> ... - - scripts.ocm.config.ocm.software The config type scripts.ocm.config.ocm.software can be used to define transfer scripts: @@ -161,7 +153,6 @@ The following configuration types are supported: - ### Examples ``` diff --git a/docs/reference/ocm_create_componentarchive.md b/docs/reference/ocm_create_componentarchive.md index cdd5b0156..d6e112ce0 100644 --- a/docs/reference/ocm_create_componentarchive.md +++ b/docs/reference/ocm_create_componentarchive.md @@ -36,11 +36,9 @@ It the option --scheme is given, the given component descriptor for The following schema versions are supported: - ocm.software/v3alpha1: - - v2 (default): - ### SEE ALSO ##### Parents diff --git a/docs/reference/ocm_get_componentversions.md b/docs/reference/ocm_get_componentversions.md index eceb2a287..2d4de320b 100644 --- a/docs/reference/ocm_get_componentversions.md +++ b/docs/reference/ocm_get_componentversions.md @@ -85,10 +85,8 @@ It the option --scheme is given, the given component descriptor is The following schema versions are supported: - ocm.software/v3alpha1: - - v2: - With the option --output the output mode can be selected. The following modes are supported: - JSON diff --git a/docs/reference/ocm_get_credentials.md b/docs/reference/ocm_get_credentials.md index 910c83f8a..8f6b8e19a 100644 --- a/docs/reference/ocm_get_credentials.md +++ b/docs/reference/ocm_get_credentials.md @@ -25,14 +25,11 @@ For the following usage contexts with matchers and standard identity matchers ex It matches the Buildcredentials.ocm.software consumer type and additionally acts like the hostpath type. - - OCIRegistry: OCI registry credential matcher It matches the OCIRegistry consumer type and additionally acts like the hostpath type. - - exact: exact match of given pattern set - - hostpath: Host and path based credential matcher This matcher works on the following properties: @@ -42,10 +39,8 @@ For the following usage contexts with matchers and standard identity matchers ex - *pathprefix* (optional): a path prefix to match. The element with the most matching path components is selected (separator is /). - - partial: complete match of given pattern ignoring additional attributes - The used matcher is derived from the consumer attribute type. For all other consumer types a matcher matching all attributes will be used. The usage of a dedicated matcher can be enforced by the option --matcher. diff --git a/docs/reference/ocm_sign_componentversions.md b/docs/reference/ocm_sign_componentversions.md index ed2da770c..b48cfc539 100644 --- a/docs/reference/ocm_sign_componentversions.md +++ b/docs/reference/ocm_sign_componentversions.md @@ -92,28 +92,21 @@ given signature name will be verified, instead of recreated. The following signing types are supported with option --algorithm: - RSASSA-PKCS1-V1_5 (default): - - rsa-signingsservice: - The following normalization modes are supported with option --normalization: - jsonNormalisation/v1 (default): - - jsonNormalisation/v2: - The following hash modes are supported with option --hash: - NO-DIGEST: - - sha256 (default): - - sha512: - If a component lookup for building a reference closure is required the --lookup option can be used to specify a fallback lookup repository. diff --git a/go.mod b/go.mod index 6376423f4..3a1415ef2 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/open-component-model/ocm go 1.18 replace ( - github.com/spf13/cobra => github.com/mandelsoft/cobra v1.4.1-0.20220816123314-fd3b703d78bf + github.com/spf13/cobra => github.com/mandelsoft/cobra v1.5.1-0.20221030110806-c236cde1e2bd github.com/spf13/pflag => github.com/mandelsoft/pflag v0.0.0-20221101220350-bbff7bc4f7b5 ) @@ -20,7 +20,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.0.3-0.20211202193544-a5463b7f9c84 github.com/sirupsen/logrus v1.9.0 - github.com/spf13/cobra v1.5.0 + github.com/spf13/cobra v1.6.1 github.com/xeipuuv/gojsonschema v1.2.0 golang.org/x/net v0.0.0-20220722155237-a158d28d115b golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect diff --git a/go.sum b/go.sum index 621678c83..3b958ecc5 100644 --- a/go.sum +++ b/go.sum @@ -810,8 +810,8 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mandelsoft/cobra v1.4.1-0.20220816123314-fd3b703d78bf h1:rlZ3V+9Kkyk8j07D18kDmMFTZ60ecDSGHu5nP5BYPzI= -github.com/mandelsoft/cobra v1.4.1-0.20220816123314-fd3b703d78bf/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/mandelsoft/cobra v1.5.1-0.20221030110806-c236cde1e2bd h1:tORGCEWEUztpMw5JRkKINckDRkd+3xgyHwfCWRr9NMg= +github.com/mandelsoft/cobra v1.5.1-0.20221030110806-c236cde1e2bd/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/mandelsoft/filepath v0.0.0-20200909114706-3df73d378d55/go.mod h1:n4xEiUD2HNHnn2w5ZKF0qgjDecHVCWAl5DxZ7+pcFU8= github.com/mandelsoft/filepath v0.0.0-20220503095057-4432a2285b68 h1:99GWPlKVS110Cm+/YRVRaUJN5CpTeWFazWg2sXJdf70= github.com/mandelsoft/filepath v0.0.0-20220503095057-4432a2285b68/go.mod h1:n4xEiUD2HNHnn2w5ZKF0qgjDecHVCWAl5DxZ7+pcFU8= diff --git a/pkg/contexts/ocm/accessmethods/github/cli.go b/pkg/contexts/ocm/accessmethods/github/cli.go index 247eadb7b..a542c021a 100644 --- a/pkg/contexts/ocm/accessmethods/github/cli.go +++ b/pkg/contexts/ocm/accessmethods/github/cli.go @@ -28,9 +28,9 @@ func AddConfig(opts flagsets.ConfigOptions, config flagsets.Config) error { var usage = ` This method implements the access of the content of a git commit stored in a GitHub repository. +` -Supported specification version is v1 - +var formatV1 = ` The type specific specification fields are: - **repoUrl** *string* diff --git a/pkg/contexts/ocm/accessmethods/github/method.go b/pkg/contexts/ocm/accessmethods/github/method.go index 16e058967..b12a37e64 100644 --- a/pkg/contexts/ocm/accessmethods/github/method.go +++ b/pkg/contexts/ocm/accessmethods/github/method.go @@ -44,10 +44,10 @@ const CONSUMER_TYPE = "Github" const ShaLength = 40 func init() { - cpi.RegisterAccessType(cpi.NewAccessSpecType(Type, &AccessSpec{}, usage, ConfigHandler())) - cpi.RegisterAccessType(cpi.NewAccessSpecType(TypeV1, &AccessSpec{}, "")) - cpi.RegisterAccessType(cpi.NewAccessSpecType(LegacyType, &AccessSpec{}, "")) - cpi.RegisterAccessType(cpi.NewAccessSpecType(LegacyTypeV1, &AccessSpec{}, "")) + cpi.RegisterAccessType(cpi.NewAccessSpecType(Type, &AccessSpec{}, cpi.WithDescription(usage))) + cpi.RegisterAccessType(cpi.NewAccessSpecType(TypeV1, &AccessSpec{}, cpi.WithFormatSpec(formatV1), cpi.WithConfigHandler(ConfigHandler()))) + cpi.RegisterAccessType(cpi.NewAccessSpecType(LegacyType, &AccessSpec{})) + cpi.RegisterAccessType(cpi.NewAccessSpecType(LegacyTypeV1, &AccessSpec{})) } func Is(spec cpi.AccessSpec) bool { diff --git a/pkg/contexts/ocm/accessmethods/localblob/cli.go b/pkg/contexts/ocm/accessmethods/localblob/cli.go index cf1a96fb8..d5cac590f 100644 --- a/pkg/contexts/ocm/accessmethods/localblob/cli.go +++ b/pkg/contexts/ocm/accessmethods/localblob/cli.go @@ -38,9 +38,9 @@ but it MUST provide an implementation for this method. Regardless of the chosen implementation the attribute specification is defined globally the same. +` -Supported specification version is v1 - +var formatV1 = ` The type specific specification fields are: - **localReference** *string* diff --git a/pkg/contexts/ocm/accessmethods/localblob/method.go b/pkg/contexts/ocm/accessmethods/localblob/method.go index b4fa33574..09f33326f 100644 --- a/pkg/contexts/ocm/accessmethods/localblob/method.go +++ b/pkg/contexts/ocm/accessmethods/localblob/method.go @@ -18,8 +18,8 @@ const Type = "localBlob" const TypeV1 = Type + runtime.VersionSeparator + "v1" func init() { - cpi.RegisterAccessType(cpi.NewConvertedAccessSpecType(Type, LocalBlobV1, usage, ConfigHandler())) - cpi.RegisterAccessType(cpi.NewConvertedAccessSpecType(TypeV1, LocalBlobV1, "")) + cpi.RegisterAccessType(cpi.NewConvertedAccessSpecType(Type, LocalBlobV1, cpi.WithDescription(usage))) + cpi.RegisterAccessType(cpi.NewConvertedAccessSpecType(TypeV1, LocalBlobV1, cpi.WithFormatSpec(formatV1), cpi.WithConfigHandler(ConfigHandler()))) } func Is(spec cpi.AccessSpec) bool { diff --git a/pkg/contexts/ocm/accessmethods/localfsblob/method.go b/pkg/contexts/ocm/accessmethods/localfsblob/method.go index a1d8fbdfd..18ff12e5b 100644 --- a/pkg/contexts/ocm/accessmethods/localfsblob/method.go +++ b/pkg/contexts/ocm/accessmethods/localfsblob/method.go @@ -17,8 +17,8 @@ const TypeV1 = Type + "/v1" // Keep old access method and map generic one to this implementation for component archives func init() { - cpi.RegisterAccessType(cpi.NewConvertedAccessSpecType(Type, LocalFilesystemBlobV1, "")) - cpi.RegisterAccessType(cpi.NewConvertedAccessSpecType(TypeV1, LocalFilesystemBlobV1, "")) + cpi.RegisterAccessType(cpi.NewConvertedAccessSpecType(Type, LocalFilesystemBlobV1)) + cpi.RegisterAccessType(cpi.NewConvertedAccessSpecType(TypeV1, LocalFilesystemBlobV1)) } // New creates a new localFilesystemBlob accessor. diff --git a/pkg/contexts/ocm/accessmethods/localociblob/method.go b/pkg/contexts/ocm/accessmethods/localociblob/method.go index 4833f59c3..569af9574 100644 --- a/pkg/contexts/ocm/accessmethods/localociblob/method.go +++ b/pkg/contexts/ocm/accessmethods/localociblob/method.go @@ -18,8 +18,8 @@ const Type = "localOciBlob" const TypeV1 = Type + runtime.VersionSeparator + "v1" func init() { - cpi.RegisterAccessType(cpi.NewAccessSpecType(Type, &AccessSpec{}, "")) - cpi.RegisterAccessType(cpi.NewAccessSpecType(TypeV1, &AccessSpec{}, "")) + cpi.RegisterAccessType(cpi.NewAccessSpecType(Type, &AccessSpec{})) + cpi.RegisterAccessType(cpi.NewAccessSpecType(TypeV1, &AccessSpec{})) } // New creates a new LocalOCIBlob accessor. diff --git a/pkg/contexts/ocm/accessmethods/none/method.go b/pkg/contexts/ocm/accessmethods/none/method.go index f62402dac..1c9d2031e 100644 --- a/pkg/contexts/ocm/accessmethods/none/method.go +++ b/pkg/contexts/ocm/accessmethods/none/method.go @@ -12,13 +12,13 @@ import ( "github.com/open-component-model/ocm/pkg/runtime" ) -// Type is the access type for a blob in an OCI repository. +// Type is the access type for no blob. const Type = "none" const TypeV1 = Type + runtime.VersionSeparator + "v1" func init() { - cpi.RegisterAccessType(cpi.NewAccessSpecType(Type, &AccessSpec{}, "")) - cpi.RegisterAccessType(cpi.NewAccessSpecType(TypeV1, &AccessSpec{}, "")) + cpi.RegisterAccessType(cpi.NewAccessSpecType(Type, &AccessSpec{}, cpi.WithDescription("dummy resource with no access"))) + cpi.RegisterAccessType(cpi.NewAccessSpecType(TypeV1, &AccessSpec{})) } // New creates a new OCIBlob accessor. diff --git a/pkg/contexts/ocm/accessmethods/ociartefact/cli.go b/pkg/contexts/ocm/accessmethods/ociartefact/cli.go index bb37a16b5..662f1b991 100644 --- a/pkg/contexts/ocm/accessmethods/ociartefact/cli.go +++ b/pkg/contexts/ocm/accessmethods/ociartefact/cli.go @@ -23,9 +23,9 @@ func AddConfig(opts flagsets.ConfigOptions, config flagsets.Config) error { var usage = ` This method implements the access of an OCI artefact stored in an OCI registry. +` -Supported specification version is v1 - +var formatV1 = ` The type specific specification fields are: - **imageReference** *string* diff --git a/pkg/contexts/ocm/accessmethods/ociartefact/method.go b/pkg/contexts/ocm/accessmethods/ociartefact/method.go index 76df0fe91..f6cb07e27 100644 --- a/pkg/contexts/ocm/accessmethods/ociartefact/method.go +++ b/pkg/contexts/ocm/accessmethods/ociartefact/method.go @@ -32,11 +32,11 @@ const ( ) func init() { - cpi.RegisterAccessType(cpi.NewAccessSpecType(Type, &AccessSpec{}, usage, ConfigHandler())) - cpi.RegisterAccessType(cpi.NewAccessSpecType(TypeV1, &AccessSpec{}, "")) + cpi.RegisterAccessType(cpi.NewAccessSpecType(Type, &AccessSpec{}, cpi.WithDescription(usage))) + cpi.RegisterAccessType(cpi.NewAccessSpecType(TypeV1, &AccessSpec{}, cpi.WithFormatSpec(formatV1), cpi.WithConfigHandler(ConfigHandler()))) - cpi.RegisterAccessType(cpi.NewAccessSpecType(LegacyType, &AccessSpec{}, "")) - cpi.RegisterAccessType(cpi.NewAccessSpecType(LegacyTypeV1, &AccessSpec{}, "")) + cpi.RegisterAccessType(cpi.NewAccessSpecType(LegacyType, &AccessSpec{})) + cpi.RegisterAccessType(cpi.NewAccessSpecType(LegacyTypeV1, &AccessSpec{})) } func Is(spec cpi.AccessSpec) bool { diff --git a/pkg/contexts/ocm/accessmethods/ociblob/cli.go b/pkg/contexts/ocm/accessmethods/ociblob/cli.go index 674167429..19ac79750 100644 --- a/pkg/contexts/ocm/accessmethods/ociblob/cli.go +++ b/pkg/contexts/ocm/accessmethods/ociblob/cli.go @@ -29,9 +29,9 @@ func AddConfig(opts flagsets.ConfigOptions, config flagsets.Config) error { var usage = ` This method implements the access of an OCI blob stored in an OCI repository. +` -Supported specification version is v1 - +var formatV1 = ` The type specific specification fields are: - **imageReference** *string* @@ -49,5 +49,4 @@ The type specific specification fields are: - **size** *integer* The size of the blob - ` diff --git a/pkg/contexts/ocm/accessmethods/ociblob/method.go b/pkg/contexts/ocm/accessmethods/ociblob/method.go index c2a97bb13..46d5699e5 100644 --- a/pkg/contexts/ocm/accessmethods/ociblob/method.go +++ b/pkg/contexts/ocm/accessmethods/ociblob/method.go @@ -24,8 +24,8 @@ const Type = "ociBlob" const TypeV1 = Type + runtime.VersionSeparator + "v1" func init() { - cpi.RegisterAccessType(cpi.NewAccessSpecType(Type, &AccessSpec{}, usage, ConfigHandler())) - cpi.RegisterAccessType(cpi.NewAccessSpecType(TypeV1, &AccessSpec{}, "")) + cpi.RegisterAccessType(cpi.NewAccessSpecType(Type, &AccessSpec{}, cpi.WithDescription(usage))) + cpi.RegisterAccessType(cpi.NewAccessSpecType(TypeV1, &AccessSpec{}, cpi.WithFormatSpec(formatV1), cpi.WithConfigHandler(ConfigHandler()))) } // New creates a new OCIBlob accessor. diff --git a/pkg/contexts/ocm/accessmethods/plugin/type.go b/pkg/contexts/ocm/accessmethods/plugin/type.go index 5c1284e56..f71fa8770 100644 --- a/pkg/contexts/ocm/accessmethods/plugin/type.go +++ b/pkg/contexts/ocm/accessmethods/plugin/type.go @@ -17,9 +17,12 @@ type accessType struct { var _ cpi.AccessType = (*accessType)(nil) -func NewType(name string, desc string, p plugin.Plugin) cpi.AccessType { +func NewType(name string, p plugin.Plugin, desc, format string) cpi.AccessType { + if format != "" { + format = "\n" + format + } t := &accessType{ - AccessType: cpi.NewAccessSpecType(name, &AccessSpec{}, desc), + AccessType: cpi.NewAccessSpecType(name, &AccessSpec{}, cpi.WithDescription(desc), cpi.WithFormatSpec(format)), plug: p, } return t diff --git a/pkg/contexts/ocm/accessmethods/s3/cli.go b/pkg/contexts/ocm/accessmethods/s3/cli.go index 7f3857392..efb31cbf5 100644 --- a/pkg/contexts/ocm/accessmethods/s3/cli.go +++ b/pkg/contexts/ocm/accessmethods/s3/cli.go @@ -31,9 +31,9 @@ func AddConfig(opts flagsets.ConfigOptions, config flagsets.Config) error { var usage = ` This method implements the access of a blob stored in an S3 bucket. +` -Supported specification version is v1 - +var formatV1 = ` The type specific specification fields are: - **region** (optional) *string* diff --git a/pkg/contexts/ocm/accessmethods/s3/method.go b/pkg/contexts/ocm/accessmethods/s3/method.go index fa678cc67..ac9f570cf 100644 --- a/pkg/contexts/ocm/accessmethods/s3/method.go +++ b/pkg/contexts/ocm/accessmethods/s3/method.go @@ -30,8 +30,8 @@ const ( ) func init() { - cpi.RegisterAccessType(cpi.NewAccessSpecType(Type, &AccessSpec{}, usage, ConfigHandler())) - cpi.RegisterAccessType(cpi.NewAccessSpecType(TypeV1, &AccessSpec{}, "")) + cpi.RegisterAccessType(cpi.NewAccessSpecType(Type, &AccessSpec{}, cpi.WithDescription(usage))) + cpi.RegisterAccessType(cpi.NewAccessSpecType(TypeV1, &AccessSpec{}, cpi.WithFormatSpec(formatV1), cpi.WithConfigHandler(ConfigHandler()))) } // AccessSpec describes the access for a GitHub registry. diff --git a/pkg/contexts/ocm/cpi/accessspec_options.go b/pkg/contexts/ocm/cpi/accessspec_options.go new file mode 100644 index 000000000..654200cf6 --- /dev/null +++ b/pkg/contexts/ocm/cpi/accessspec_options.go @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package cpi + +import ( + "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" +) + +//////////////////////////////////////////////////////////////////////////////// +// Access Type Options + +type AccessSpecOptionTarget interface { + SetFormat(string) + SetDescription(string) + SetConfigHandler(flagsets.ConfigOptionTypeSetHandler) +} + +type AccessSpecTypeOption interface { + ApplyToAccessSpecOptionTarget(AccessSpecOptionTarget) +} + +//////////////////////////////////////////////////////////////////////////////// + +type formatOption struct { + value string +} + +func WithFormatSpec(value string) AccessSpecTypeOption { + return formatOption{value} +} + +func (o formatOption) ApplyToAccessSpecOptionTarget(t AccessSpecOptionTarget) { + t.SetFormat(o.value) +} + +//////////////////////////////////////////////////////////////////////////////// + +type descriptionOption struct { + value string +} + +func WithDescription(value string) AccessSpecTypeOption { + return descriptionOption{value} +} + +func (o descriptionOption) ApplyToAccessSpecOptionTarget(t AccessSpecOptionTarget) { + t.SetDescription(o.value) +} + +//////////////////////////////////////////////////////////////////////////////// + +type configOption struct { + value flagsets.ConfigOptionTypeSetHandler +} + +func WithConfigHandler(value flagsets.ConfigOptionTypeSetHandler) AccessSpecTypeOption { + return configOption{value} +} + +func (o configOption) ApplyToAccessSpecOptionTarget(t AccessSpecOptionTarget) { + t.SetConfigHandler(o.value) +} diff --git a/pkg/contexts/ocm/cpi/accessspecs_converted.go b/pkg/contexts/ocm/cpi/accessspecs_converted.go index 2b8acdae1..a7fc7e881 100644 --- a/pkg/contexts/ocm/cpi/accessspecs_converted.go +++ b/pkg/contexts/ocm/cpi/accessspecs_converted.go @@ -9,11 +9,9 @@ import ( "github.com/sirupsen/logrus" - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" "github.com/open-component-model/ocm/pkg/contexts/ocm/internal" "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 AccessSpecConverter interface { @@ -59,16 +57,18 @@ var ( _ runtime.TypedObjectEncoder = &ConvertedAccessType{} ) -func NewConvertedAccessSpecType(name string, v AccessSpecVersion, desc string, handler ...flagsets.ConfigOptionTypeSetHandler) *ConvertedAccessType { - return &ConvertedAccessType{ +func NewConvertedAccessSpecType(name string, v AccessSpecVersion, opts ...AccessSpecTypeOption) *ConvertedAccessType { + t := &ConvertedAccessType{ accessType: accessType{ ObjectVersionedType: runtime.NewVersionedObjectType(name), TypedObjectDecoder: v, - description: desc, - handler: utils.Optional(handler...), }, AccessSpecVersion: v, } + for _, o := range opts { + o.ApplyToAccessSpecOptionTarget(accessTypeTarget{&t.accessType}) + } + return t } func (t *ConvertedAccessType) Encode(obj runtime.TypedObject, m runtime.Marshaler) ([]byte, error) { diff --git a/pkg/contexts/ocm/cpi/accessspecs_simple.go b/pkg/contexts/ocm/cpi/accessspecs_simple.go index 94ae4da32..f98372ec0 100644 --- a/pkg/contexts/ocm/cpi/accessspecs_simple.go +++ b/pkg/contexts/ocm/cpi/accessspecs_simple.go @@ -10,30 +10,54 @@ import ( "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" "github.com/open-component-model/ocm/pkg/contexts/ocm/internal" "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" ) +type accessTypeTarget struct { + *accessType +} + +func (t accessTypeTarget) SetDescription(value string) { + t.description = value +} + +func (t accessTypeTarget) SetFormat(value string) { + t.format = value +} + +func (t accessTypeTarget) SetConfigHandler(value flagsets.ConfigOptionTypeSetHandler) { + t.handler = value +} + +//////////////////////////////////////////////////////////////////////////////// + type accessType struct { runtime.ObjectVersionedType runtime.TypedObjectDecoder description string + format string handler flagsets.ConfigOptionTypeSetHandler } -func NewAccessSpecType(name string, proto internal.AccessSpec, desc string, handler ...flagsets.ConfigOptionTypeSetHandler) internal.AccessType { - return &accessType{ +func NewAccessSpecType(name string, proto internal.AccessSpec, opts ...AccessSpecTypeOption) AccessType { + t := accessTypeTarget{&accessType{ ObjectVersionedType: runtime.NewVersionedObjectType(name), TypedObjectDecoder: runtime.MustNewDirectDecoder(proto), - description: desc, - handler: utils.Optional(handler...), + }} + for _, o := range opts { + o.ApplyToAccessSpecOptionTarget(t) } + return t.accessType } func (t *accessType) ConfigOptionTypeSetHandler() flagsets.ConfigOptionTypeSetHandler { return t.handler } -func (t *accessType) Description(cli bool) string { +func (t *accessType) Description() string { + return t.description +} + +func (t *accessType) Format(cli bool) string { group := "" if t.handler != nil && cli { opts := t.handler.OptionTypeNames() @@ -45,5 +69,5 @@ func (t *accessType) Description(cli bool) string { group = "\nOptions used to configure fields: " + strings.Join(names, ", ") } } - return t.description + group + return t.format + group } diff --git a/pkg/contexts/ocm/internal/accesstypes.go b/pkg/contexts/ocm/internal/accesstypes.go index 6b6680b55..3c7807674 100644 --- a/pkg/contexts/ocm/internal/accesstypes.go +++ b/pkg/contexts/ocm/internal/accesstypes.go @@ -20,7 +20,8 @@ type AccessType interface { ConfigOptionTypeSetHandler() flagsets.ConfigOptionTypeSetHandler - Description(cli bool) string + Description() string + Format(cli bool) string } type AccessMethodSupport interface { diff --git a/pkg/contexts/ocm/plugin/cache/cache.go b/pkg/contexts/ocm/plugin/cache/cache.go index ad91e8d87..655949b5a 100644 --- a/pkg/contexts/ocm/plugin/cache/cache.go +++ b/pkg/contexts/ocm/plugin/cache/cache.go @@ -150,7 +150,7 @@ func (c *cacheImpl) RegisterExtensions(ctx ocm.Context) error { if m.Version != "" { name = name + runtime.VersionSeparator + m.Version } - ctx.AccessMethods().Register(name, access.NewType(name, m.Long, p)) + ctx.AccessMethods().Register(name, access.NewType(name, p, m.Description, m.Format)) } } return nil diff --git a/pkg/contexts/ocm/plugin/internal/descriptor.go b/pkg/contexts/ocm/plugin/internal/descriptor.go index 0707d42e4..644b9b206 100644 --- a/pkg/contexts/ocm/plugin/internal/descriptor.go +++ b/pkg/contexts/ocm/plugin/internal/descriptor.go @@ -4,6 +4,10 @@ package internal +import ( + "fmt" +) + const VERSION = "v1" type Descriptor struct { @@ -14,11 +18,27 @@ type Descriptor struct { Long string `json:"description"` AccessMethods []AccessMethodDescriptor `json:"accessMethods,omitempty"` + Uploaders []UploaderDescriptor `json:"uploaders,omitempty"` +} + +type UploaderKey struct { + ArtifactType string `json:"artifactType"` + MediaType string `json:"mediaType"` +} + +func (k UploaderKey) String() string { + return fmt.Sprintf("%s:%s", k.ArtifactType, k.MediaType) +} + +type UploaderDescriptor struct { + Name string `json:"name"` + Description string `json:"description"` + Costraints []UploaderKey `json:"constraints"` } type AccessMethodDescriptor struct { - Name string `json:"name"` - Version string `json:"version,omitempty"` - Short string `json:"shortDescription"` - Long string `json:"description"` + Name string `json:"name"` + Version string `json:"version,omitempty"` + Description string `json:"description"` + Format string `json:"format"` } diff --git a/pkg/contexts/ocm/plugin/internal/lookup.go b/pkg/contexts/ocm/plugin/internal/lookup.go new file mode 100644 index 000000000..e835d957b --- /dev/null +++ b/pkg/contexts/ocm/plugin/internal/lookup.go @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package internal + +import ( + "strings" +) + +type Registry[H any] struct { + mappings map[UploaderKey]H +} + +func NewRegistry[H any]() *Registry[H] { + return &Registry[H]{ + mappings: map[UploaderKey]H{}, + } +} + +func (p *Registry[H]) lookupMedia(key UploaderKey) (H, bool) { + var zero H + for { + if h, ok := p.mappings[key]; ok { + return h, true + } + if i := strings.LastIndex(key.MediaType, "+"); i > 0 { + key.MediaType = key.MediaType[:i] + } else { + break + } + } + return zero, false +} + +func (p *Registry[H]) GetHandler(key UploaderKey) H { + return p.mappings[key] +} + +func (p *Registry[H]) LookupHandler(arttype, mediatype string) (H, bool) { + key := UploaderKey{ + ArtifactType: arttype, + MediaType: mediatype, + } + + h, ok := p.lookupMedia(key) + if ok { + return h, ok + } + + key.MediaType = "" + if h, ok := p.mappings[key]; ok { + return h, ok + } + + key.MediaType = mediatype + key.ArtifactType = "" + return p.lookupMedia(key) +} + +func (p *Registry[H]) Register(key UploaderKey, h H) { + p.mappings[key] = h +} diff --git a/pkg/contexts/ocm/plugin/internal/lookup_test.go b/pkg/contexts/ocm/plugin/internal/lookup_test.go new file mode 100644 index 000000000..cc5f8e646 --- /dev/null +++ b/pkg/contexts/ocm/plugin/internal/lookup_test.go @@ -0,0 +1,80 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package internal_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/internal" +) + +var _ = Describe("lookup", func() { + var reg *internal.Registry[string] + + BeforeEach(func() { + reg = internal.NewRegistry[string]() + }) + + It("looks up complete", func() { + reg.Register(internal.UploaderKey{"a", "m"}, "test") + reg.Register(internal.UploaderKey{"a", "m1"}, "testm") + reg.Register(internal.UploaderKey{"a1", "m"}, "testa") + + h, ok := reg.LookupHandler("a", "m") + Expect(ok).To(BeTrue()) + Expect(h).To(Equal("test")) + }) + + It("looks up partial artifact", func() { + reg.Register(internal.UploaderKey{"a", ""}, "test") + reg.Register(internal.UploaderKey{"a", "m1"}, "testm") + reg.Register(internal.UploaderKey{"a1", "m"}, "testa") + + h, ok := reg.LookupHandler("a", "m") + Expect(ok).To(BeTrue()) + Expect(h).To(Equal("test")) + }) + + It("looks up partial media", func() { + reg.Register(internal.UploaderKey{"", "m"}, "test") + reg.Register(internal.UploaderKey{"a", "m1"}, "testm") + reg.Register(internal.UploaderKey{"a1", "m"}, "testa") + + h, ok := reg.LookupHandler("a", "m") + Expect(ok).To(BeTrue()) + Expect(h).To(Equal("test")) + }) + + It("looks complete with media sub type", func() { + reg.Register(internal.UploaderKey{"a", "m"}, "test") + reg.Register(internal.UploaderKey{"a", "m1"}, "testm") + reg.Register(internal.UploaderKey{"a1", "m"}, "testa") + + h, ok := reg.LookupHandler("a", "m+tar") + Expect(ok).To(BeTrue()) + Expect(h).To(Equal("test")) + }) + + It("looks partial with media sub type", func() { + reg.Register(internal.UploaderKey{"", "m"}, "test") + reg.Register(internal.UploaderKey{"a", "m1"}, "testm") + reg.Register(internal.UploaderKey{"a1", "m"}, "testa") + + h, ok := reg.LookupHandler("a", "m+tar") + Expect(ok).To(BeTrue()) + Expect(h).To(Equal("test")) + }) + + It("prefers art", func() { + reg.Register(internal.UploaderKey{"", "m"}, "testm") + reg.Register(internal.UploaderKey{"a", ""}, "test") + reg.Register(internal.UploaderKey{"a1", "m"}, "testa") + + h, ok := reg.LookupHandler("a", "m+tar") + Expect(ok).To(BeTrue()) + Expect(h).To(Equal("test")) + }) +}) diff --git a/pkg/contexts/ocm/plugin/internal/suite_test.go b/pkg/contexts/ocm/plugin/internal/suite_test.go new file mode 100644 index 000000000..ba2389e5a --- /dev/null +++ b/pkg/contexts/ocm/plugin/internal/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 internal_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestConfig(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Plugin Internal Test Suite") +} diff --git a/pkg/contexts/ocm/plugin/ppi/accessmethod.go b/pkg/contexts/ocm/plugin/ppi/accessmethod.go deleted file mode 100644 index e0d972344..000000000 --- a/pkg/contexts/ocm/plugin/ppi/accessmethod.go +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. -// -// SPDX-License-Identifier: Apache-2.0 - -package ppi - -import ( - "github.com/open-component-model/ocm/pkg/runtime" -) - -type AccessSpec runtime.VersionedTypedObject - -type AccessSpecProvider func() AccessSpec diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/cmd.go index faf88a2b6..2d75c491b 100644 --- a/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/cmd.go +++ b/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/cmd.go @@ -9,7 +9,6 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/get" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/put" "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/validate" ) @@ -24,6 +23,5 @@ func New(p ppi.Plugin) *cobra.Command { cmd.AddCommand(validate.New(p)) cmd.AddCommand(get.New(p)) - cmd.AddCommand(put.New(p)) return cmd } diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/get/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/get/cmd.go index 8ab4c211c..a2722c106 100644 --- a/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/get/cmd.go +++ b/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/get/cmd.go @@ -64,7 +64,7 @@ func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error { m := p.GetAccessMethod(spec.GetKind(), spec.GetVersion()) if m == nil { - return errors.ErrUnknown(errors.KIND_ACCESSMETHOD, spec.GetType()) + return errors.ErrUnknown(ppi.KIND_ACCESSMETHOD, spec.GetType()) } _, err = m.ValidateSpecification(p, spec) if err != nil { diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/app.go b/pkg/contexts/ocm/plugin/ppi/cmds/app.go index cc059b11e..437a87e55 100644 --- a/pkg/contexts/ocm/plugin/ppi/cmds/app.go +++ b/pkg/contexts/ocm/plugin/ppi/cmds/app.go @@ -6,6 +6,7 @@ package cmds import ( "encoding/json" + "os" "github.com/spf13/cobra" @@ -13,6 +14,7 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod" "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/info" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/upload" ) type PluginCommand struct { @@ -35,10 +37,14 @@ func NewPluginCommand(p ppi.Plugin) *PluginCommand { SilenceErrors: true, } + cmd.SetOut(os.Stdout) + cmd.SetErr(os.Stderr) + cobrautils.TweakCommand(cmd, nil) cmd.AddCommand(info.New(p)) cmd.AddCommand(accessmethod.New(p)) + cmd.AddCommand(upload.New(p)) cmd.InitDefaultHelpCmd() var help *cobra.Command diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/put/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/upload/cmd.go similarity index 67% rename from pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/put/cmd.go rename to pkg/contexts/ocm/plugin/ppi/cmds/upload/cmd.go index 986841ea1..233e23679 100644 --- a/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/put/cmd.go +++ b/pkg/contexts/ocm/plugin/ppi/cmds/upload/cmd.go @@ -2,13 +2,13 @@ // // SPDX-License-Identifier: Apache-2.0 -package put +package upload import ( "encoding/json" + "fmt" "io" "os" - "strings" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -20,14 +20,14 @@ import ( "github.com/open-component-model/ocm/pkg/runtime" ) -const Name = "put" +const Name = "upload" func New(p ppi.Plugin) *cobra.Command { opts := Options{} cmd := &cobra.Command{ - Use: Name + " ", - Short: "put blob", + Use: Name + " ", + Short: "upload blob to external repository", Long: "", Args: cobra.ExactArgs(1), PersistentPreRunE: func(cmd *cobra.Command, args []string) error { @@ -44,45 +44,34 @@ func New(p ppi.Plugin) *cobra.Command { type Options struct { Credentials credentials.DirectCredentials - MediaType string - MethodName string - MethodVersion string + MediaType string + ArtifactType string + Hint string - Hint string + Repository json.RawMessage } func (o *Options) AddFlags(fs *pflag.FlagSet) { flag.YAMLVarP(fs, &o.Credentials, "credentials", "c", nil, "credentials") flag.StringMapVarPA(fs, &o.Credentials, "credential", "C", nil, "dedicated credential value") fs.StringVarP(&o.MediaType, "mediaType", "m", "", "media type of input blob") - fs.StringVarP(&o.Hint, "hint", "h", "", "reference hint for storing blob") + fs.StringVarP(&o.ArtifactType, "artifactType", "a", "", "artifact type of input blob") + fs.StringVarP(&o.Hint, "hint", "H", "", "reference hint for storing blob") } func (o *Options) Complete(args []string) error { - fields := strings.Split(args[0], runtime.VersionSeparator) - o.MethodName = fields[0] - if len(fields) > 1 { - o.MethodVersion = fields[1] - } - if len(fields) > 2 { - return errors.ErrInvalid(errors.KIND_ACCESSMETHOD, args[0]) + if err := runtime.DefaultYAMLEncoding.Unmarshal([]byte(args[0]), o.Repository); err != nil { + return errors.Wrapf(err, "invalid repository specification") } return nil } -func (o *Options) Method() string { - if o.MethodVersion == "" { - return o.MethodName - } - return o.MethodName + runtime.VersionSeparator + o.MethodVersion -} - func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error { - m := p.GetAccessMethod(opts.MethodName, opts.MethodVersion) - if m == nil { - return errors.ErrUnknown(errors.KIND_ACCESSMETHOD, opts.Method()) + u := p.GetUploader(opts.ArtifactType, opts.MediaType) + if u == nil { + return errors.ErrNotFound(ppi.KIND_UPLOADER, fmt.Sprintf("%s:%s", opts.ArtifactType, opts.MediaType)) } - w, h, err := m.Writer(p, opts.MediaType, opts.Credentials) + w, h, err := u.Writer(p, opts.ArtifactType, opts.MediaType, opts.Hint, opts.Credentials) if err != nil { return err } diff --git a/pkg/contexts/ocm/plugin/ppi/interface.go b/pkg/contexts/ocm/plugin/ppi/interface.go index f6230205e..921d08207 100644 --- a/pkg/contexts/ocm/plugin/ppi/interface.go +++ b/pkg/contexts/ocm/plugin/ppi/interface.go @@ -9,21 +9,35 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/credentials" "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/internal" + "github.com/open-component-model/ocm/pkg/errors" "github.com/open-component-model/ocm/pkg/runtime" ) type ( - Descriptor = internal.Descriptor - AccessSpecInfo = internal.AccessSpecInfo + Descriptor = internal.Descriptor + AccessSpecInfo = internal.AccessSpecInfo + UploaderKey = internal.UploaderKey + UploaderDescriptor = internal.UploaderDescriptor + AccessMethodDescriptor = internal.AccessMethodDescriptor ) -const KIND_PLUGIN = "plugin" +const ( + KIND_PLUGIN = "plugin" + KIND_UPLOADER = "uploader" + KIND_ACCESSMETHOD = errors.KIND_ACCESSMETHOD +) type Plugin interface { Name() string Version() string Descriptor() internal.Descriptor + SetShort(s string) + SetLong(s string) + + RegisterUploader(arttype, mediatype string, u Uploader) error + GetUploader(arttype, mediatype string) Uploader + RegisterAccessMethod(m AccessMethod) error DecodeAccessSpecification(data []byte) (AccessSpec, error) GetAccessMethod(name string, version string) AccessMethod @@ -37,7 +51,22 @@ type AccessMethod interface { Name() string Version() string + // Description provides a general description for the access mehod kind. + Description() string + // Format describes the attributes of the dedicated version. + Format() string + ValidateSpecification(p Plugin, spec AccessSpec) (info *AccessSpecInfo, err error) Reader(p Plugin, spec AccessSpec, creds credentials.Credentials) (io.ReadCloser, error) - Writer(p Plugin, mediatype string, creds credentials.Credentials) (io.WriteCloser, AccessSpecProvider, error) +} + +type AccessSpec runtime.VersionedTypedObject + +type AccessSpecProvider func() AccessSpec + +type Uploader interface { + Name() string + Description() string + + Writer(p Plugin, arttyoe, mediatype string, hint string, creds credentials.Credentials) (io.WriteCloser, AccessSpecProvider, error) } diff --git a/pkg/contexts/ocm/plugin/ppi/plugin.go b/pkg/contexts/ocm/plugin/ppi/plugin.go index 64a98cfa2..1411f6f68 100644 --- a/pkg/contexts/ocm/plugin/ppi/plugin.go +++ b/pkg/contexts/ocm/plugin/ppi/plugin.go @@ -5,6 +5,8 @@ package ppi import ( + "fmt" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/internal" "github.com/open-component-model/ocm/pkg/errors" "github.com/open-component-model/ocm/pkg/runtime" @@ -16,6 +18,9 @@ type plugin struct { descriptor internal.Descriptor options Options + uploaders map[string]Uploader + mappings *internal.Registry[Uploader] + methods map[string]AccessMethod accessScheme runtime.Scheme } @@ -26,6 +31,8 @@ func NewPlugin(name string, version string) Plugin { name: name, version: version, methods: map[string]AccessMethod{}, + uploaders: map[string]Uploader{}, + mappings: internal.NewRegistry[Uploader](), accessScheme: runtime.MustNewDefaultScheme(&rt, &runtime.UnstructuredVersionedTypedObject{}, false, nil), descriptor: internal.Descriptor{ Version: internal.VERSION, @@ -51,11 +58,70 @@ func (p *plugin) Options() *Options { return &p.options } +func (p *plugin) SetLong(s string) { + p.descriptor.Long = s +} + +func (p *plugin) SetShort(s string) { + p.descriptor.Short = s +} + +func (p *plugin) RegisterUploader(arttype, mediatype string, u Uploader) error { + old := p.uploaders[u.Name()] + if old != nil && old != u { + return fmt.Errorf("uploader name %q already in use", u.Name()) + } + + var d *UploaderDescriptor + if old == nil { + d = &UploaderDescriptor{ + Name: u.Name(), + Description: u.Description(), + Costraints: []UploaderKey{}, + } + p.descriptor.Uploaders = append(p.descriptor.Uploaders, *d) + d = &p.descriptor.Uploaders[len(p.descriptor.Uploaders)-1] + } else { + for i := range p.descriptor.Uploaders { + if p.descriptor.Uploaders[i].Name == u.Name() { + d = &p.descriptor.Uploaders[i] + } + } + } + p.uploaders[u.Name()] = u + + key := UploaderKey{ + ArtifactType: arttype, + MediaType: mediatype, + } + cur := p.mappings.GetHandler(key) + if cur != nil && cur != u { + return fmt.Errorf("uploader mapping key %q already in use", key) + } + if cur == nil { + p.mappings.Register(key, u) + + if key.ArtifactType == "" { + key.ArtifactType = "*" + } + if key.MediaType == "" { + key.MediaType = "*" + } + d.Costraints = append(d.Costraints, key) + } + return nil +} + +func (p *plugin) GetUploader(arttype, mediatype string) Uploader { + u, _ := p.mappings.LookupHandler(arttype, mediatype) + return u +} + func (p *plugin) RegisterAccessMethod(m AccessMethod) error { if p.GetAccessMethod(m.Name(), m.Version()) != nil { n := m.Name() if m.Version() != "" { - n += "/" + m.Version() + n += runtime.VersionSeparator + m.Version() } return errors.ErrAlreadyExists(errors.KIND_ACCESSMETHOD, n) } @@ -63,7 +129,9 @@ func (p *plugin) RegisterAccessMethod(m AccessMethod) error { vers := m.Version() if vers == "" { meth := internal.AccessMethodDescriptor{ - Name: m.Name(), + Name: m.Name(), + Description: m.Description(), + Format: m.Format(), } p.descriptor.AccessMethods = append(p.descriptor.AccessMethods, meth) p.accessScheme.RegisterByDecoder(m.Name(), m) @@ -71,8 +139,10 @@ func (p *plugin) RegisterAccessMethod(m AccessMethod) error { p.methods[m.Name()] = m } meth := internal.AccessMethodDescriptor{ - Name: m.Name(), - Version: vers, + Name: m.Name(), + Version: vers, + Description: m.Description(), + Format: m.Format(), } p.descriptor.AccessMethods = append(p.descriptor.AccessMethods, meth) p.accessScheme.RegisterByDecoder(m.Name()+"/"+vers, m) diff --git a/pkg/contexts/ocm/plugin/ppi/utils.go b/pkg/contexts/ocm/plugin/ppi/utils.go index 163f5ce6b..0236e472c 100644 --- a/pkg/contexts/ocm/plugin/ppi/utils.go +++ b/pkg/contexts/ocm/plugin/ppi/utils.go @@ -12,11 +12,13 @@ type decoder runtime.TypedObjectDecoder type AccessMethodBase struct { decoder - name string + nameDescription + version string + format string } -func MustNewAccessMethodBase(name, version string, proto AccessSpec) AccessMethodBase { +func MustNewAccessMethodBase(name, version string, proto AccessSpec, desc string, format string) AccessMethodBase { decoder, err := runtime.NewDirectDecoder(proto) if err != nil { panic(err) @@ -24,15 +26,45 @@ func MustNewAccessMethodBase(name, version string, proto AccessSpec) AccessMetho return AccessMethodBase{ decoder: decoder, - name: name, + nameDescription: nameDescription{ + name: name, + desc: desc, + }, version: version, + format: format, } } -func (b *AccessMethodBase) Name() string { +func (b *AccessMethodBase) Version() string { + return b.version +} + +func (b *AccessMethodBase) Format() string { + return b.format +} + +//////////////////////////////////////////////////////////////////////////////// + +type UploaderBase = nameDescription + +func MustNewUploaderBase(name, desc string) UploaderBase { + return UploaderBase{ + name: name, + desc: desc, + } +} + +//////////////////////////////////////////////////////////////////////////////// + +type nameDescription struct { + name string + desc string +} + +func (b *nameDescription) Name() string { return b.name } -func (b *AccessMethodBase) Version() string { - return b.version +func (b *nameDescription) Description() string { + return b.desc } diff --git a/pkg/contexts/ocm/usage.go b/pkg/contexts/ocm/usage.go index fe40fa5f3..ab3466130 100644 --- a/pkg/contexts/ocm/usage.go +++ b/pkg/contexts/ocm/usage.go @@ -8,6 +8,7 @@ import ( "fmt" "strings" + "github.com/open-component-model/ocm/pkg/runtime" "github.com/open-component-model/ocm/pkg/utils" ) @@ -17,30 +18,59 @@ The following access methods are known by the system. Typically there is special support for the CLI artifact add commands. The following types (with the field type in the access field are handled: - ` - names := map[string][]string{} + versions := map[string]map[string]string{} descs := map[string]string{} - for _, t := range scheme.KnownTypeNames() { - base := t - i := strings.Index(t, "/") - if i > 0 { - base = t[:i] - vers := t[i+1:] - names[base] = append(names[base], vers) + // gather info for kinds and versions + for _, n := range scheme.KnownTypeNames() { + kind, vers := runtime.KindVersion(n) + + if _, ok := descs[kind]; !ok { + descs[kind] = "" + } + var set map[string]string + if set = versions[kind]; set == nil { + set = map[string]string{} + versions[kind] = set + } + if vers == "" { + vers = "v1" + } + if _, ok := set[vers]; !ok { + set[vers] = "" + } + + t := scheme.GetAccessType(n) + + desc := t.Description() + if desc != "" { + descs[kind] = desc } - desc := scheme.GetAccessType(t).Description(cli) + + desc = t.Format(cli) if desc != "" { - descs[base] = desc + set[vers] = desc } } - for _, t := range scheme.KnownTypeNames() { - desc := descs[t] + + for _, t := range utils.StringMapKeys(descs) { + desc := strings.Trim(descs[t], "\n") if desc != "" { - s = fmt.Sprintf("%s\n\n- Access type %s\n\n%s", s, t, utils.IndentLines(desc, " ")) + s = fmt.Sprintf("%s\n- Access type %s\n\n%s\n\n", s, t, utils.IndentLines(desc, " ")) + + format := "" + for _, f := range utils.StringMapKeys(versions[t]) { + desc = strings.Trim(versions[t][f], "\n") + if desc != "" { + format = fmt.Sprintf("%s\n- Version %s\n\n%s\n", format, f, utils.IndentLines(desc, " ")) + } + } + if format != "" { + s += fmt.Sprintf(" The following versions are supported:\n%s\n", strings.Trim(utils.IndentLines(format, " "), "\n")) + } } } - return s + "\n" + return s } diff --git a/pkg/runtime/versionedtype.go b/pkg/runtime/versionedtype.go index 58bfd9496..ac3aa0187 100644 --- a/pkg/runtime/versionedtype.go +++ b/pkg/runtime/versionedtype.go @@ -93,3 +93,11 @@ func (v *ObjectVersionedType) SetVersion(version string) { } } } + +func KindVersion(t string) (string, string) { + i := strings.LastIndex(t, VersionSeparator) + if i > 0 { + return t[:i], t[i+1:] + } + return t, "" +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 69c1e933e..31b1f8907 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -221,20 +221,19 @@ func IndentLines(orig string, gap string, skipfirst ...bool) string { } func JoinIndentLines(orig []string, gap string, skipfirst ...bool) string { + if len(orig) == 0 { + return "" + } skip := false for _, b := range skipfirst { skip = skip || b } s := "" - for _, l := range orig { - if !skip { - s += gap - } - s += l + "\n" - skip = false + if !skip { + s = gap } - return s + return s + strings.Join(orig, "\n"+gap) } func StringMapKeys(m interface{}) []string { From 7e9e22542d71445cf9c093ef8a3f9e22e42081ed Mon Sep 17 00:00:00 2001 From: Uwe Krueger Date: Tue, 1 Nov 2022 11:47:39 +0100 Subject: [PATCH 3/6] separate plugin dir evaluation To speed up ocm context creation with plugins, the evaluation of a plugin dir is put into a separate cache now. Context creation then just reuses known plugin dir setup to create a context binding without calling all the plugins. --- .../common/handlers/pluginhdlr/typehandler.go | 4 +- pkg/common/accessio/limitwriter.go | 58 +++++++ .../ocm/accessmethods/plugin/method_test.go | 6 +- .../ocm/attrs/plugincacheattr/attr.go | 9 +- pkg/contexts/ocm/plugin/cache/cache.go | 157 ------------------ pkg/contexts/ocm/plugin/cache/dircache.go | 56 +++++++ pkg/contexts/ocm/plugin/cache/exec.go | 51 ++++++ pkg/contexts/ocm/plugin/cache/plugin.go | 89 ++++++++++ pkg/contexts/ocm/plugin/cache/plugindir.go | 97 +++++++++++ pkg/contexts/ocm/plugin/const.go | 11 ++ pkg/contexts/ocm/plugin/plugin.go | 131 ++------------- pkg/contexts/ocm/plugin/plugin_test.go | 21 ++- pkg/contexts/ocm/plugin/plugins/plugins.go | 104 ++++++++++++ pkg/contexts/ocm/plugin/utils.go | 53 ------ 14 files changed, 509 insertions(+), 338 deletions(-) create mode 100644 pkg/common/accessio/limitwriter.go delete mode 100644 pkg/contexts/ocm/plugin/cache/cache.go create mode 100644 pkg/contexts/ocm/plugin/cache/dircache.go create mode 100644 pkg/contexts/ocm/plugin/cache/exec.go create mode 100644 pkg/contexts/ocm/plugin/cache/plugin.go create mode 100644 pkg/contexts/ocm/plugin/cache/plugindir.go create mode 100644 pkg/contexts/ocm/plugin/const.go create mode 100644 pkg/contexts/ocm/plugin/plugins/plugins.go diff --git a/cmds/ocm/commands/ocmcmds/common/handlers/pluginhdlr/typehandler.go b/cmds/ocm/commands/ocmcmds/common/handlers/pluginhdlr/typehandler.go index 95fa112a3..8f2de26d8 100644 --- a/cmds/ocm/commands/ocmcmds/common/handlers/pluginhdlr/typehandler.go +++ b/cmds/ocm/commands/ocmcmds/common/handlers/pluginhdlr/typehandler.go @@ -55,7 +55,7 @@ func (h *TypeHandler) All() ([]output.Object, error) { result := []output.Object{} for _, n := range cache.PluginNames() { - result = append(result, &Object{cache.GetPlugin(n)}) + result = append(result, &Object{cache.Get(n)}) } return result, nil } @@ -63,7 +63,7 @@ func (h *TypeHandler) All() ([]output.Object, error) { func (h *TypeHandler) Get(elemspec utils.ElemSpec) ([]output.Object, error) { cache := plugincacheattr.Get(h.octx.Context()) - p := cache.GetPlugin(elemspec.String()) + p := cache.Get(elemspec.String()) if p == nil { return nil, errors.ErrNotFound(ppi.KIND_PLUGIN, elemspec.String()) } diff --git a/pkg/common/accessio/limitwriter.go b/pkg/common/accessio/limitwriter.go new file mode 100644 index 000000000..c17136b09 --- /dev/null +++ b/pkg/common/accessio/limitwriter.go @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package accessio + +import ( + "bytes" + "io" +) + +const DESCRIPTOR_LIMIT = int64(8196 * 4) + +// LimitWriter returns a Writer that writes to w +// but stops with EOF after n bytes. +// The underlying implementation is a *LimitedWriter. +func LimitWriter(w io.Writer, n int64) io.Writer { return &LimitedWriter{w, n} } + +// A LimitedWriter writes to W but limits the amount of +// data written to just N bytes. Each call to Write +// updates N to reflect the new amount remaining. +// Write returns EOF when N <= 0 or when the underlying W returns EOF. +type LimitedWriter struct { + W io.Writer // underlying reader + N int64 // max bytes remaining +} + +func (l *LimitedWriter) Write(p []byte) (n int, err error) { + if l.N <= 0 { + return 0, io.EOF + } + if int64(len(p)) > l.N { + p = p[0:l.N] + } + n, err = l.W.Write(p) + l.N -= int64(n) + return +} + +func LimitBuffer(n int64) *LimitedBuffer { + buf := &LimitedBuffer{max: n} + buf.LimitedWriter = &LimitedWriter{&buf.buffer, n + 1} + return buf +} + +type LimitedBuffer struct { + *LimitedWriter + max int64 + buffer bytes.Buffer +} + +func (b *LimitedBuffer) Exceeded() bool { + return b.LimitedWriter.N > b.max +} + +func (b *LimitedBuffer) Bytes() []byte { + return b.buffer.Bytes() +} diff --git a/pkg/contexts/ocm/accessmethods/plugin/method_test.go b/pkg/contexts/ocm/accessmethods/plugin/method_test.go index a5bf88128..65ccbce2b 100644 --- a/pkg/contexts/ocm/accessmethods/plugin/method_test.go +++ b/pkg/contexts/ocm/accessmethods/plugin/method_test.go @@ -16,7 +16,7 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/plugincacheattr" "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/plugindirattr" metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/cache" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/plugins" "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" ) @@ -27,7 +27,7 @@ const PROVIDER = "mandelsoft" var _ = Describe("setup plugin cache", func() { var ctx ocm.Context - var registry cache.Cache + var registry plugins.Set var env *Builder var accessSpec ocm.AccessSpec @@ -46,7 +46,7 @@ someattr: value plugindirattr.Set(ctx, "testdata") registry = plugincacheattr.Get(ctx) Expect(registry.RegisterExtensions(ctx)).To(Succeed()) - p := registry.GetPlugin("test") + p := registry.Get("test") Expect(p).NotTo(BeNil()) }) diff --git a/pkg/contexts/ocm/attrs/plugincacheattr/attr.go b/pkg/contexts/ocm/attrs/plugincacheattr/attr.go index 0280d8760..0a365e914 100644 --- a/pkg/contexts/ocm/attrs/plugincacheattr/attr.go +++ b/pkg/contexts/ocm/attrs/plugincacheattr/attr.go @@ -9,6 +9,7 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/ocm" "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/plugindirattr" "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/cache" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/plugins" ) const ( @@ -17,15 +18,15 @@ const ( //////////////////////////////////////////////////////////////////////////////// -func Get(ctx ocm.Context) cache.Cache { +func Get(ctx ocm.Context) plugins.Set { path := plugindirattr.Get(ctx) // avoid dead lock reading attribute during attribute creation return ctx.GetAttributes().GetOrCreateAttribute(ATTR_KEY, func(ctx datacontext.Context) interface{} { - return cache.New(ctx.(ocm.Context), path) - }).(cache.Cache) + return plugins.New(ctx.(ocm.Context), path) + }).(plugins.Set) } -func Set(ctx ocm.Context, cache cache.Cache) error { +func Set(ctx ocm.Context, cache cache.PluginDir) error { return ctx.GetAttributes().SetAttribute(ATTR_KEY, cache) } diff --git a/pkg/contexts/ocm/plugin/cache/cache.go b/pkg/contexts/ocm/plugin/cache/cache.go deleted file mode 100644 index 655949b5a..000000000 --- a/pkg/contexts/ocm/plugin/cache/cache.go +++ /dev/null @@ -1,157 +0,0 @@ -// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. -// -// SPDX-License-Identifier: Apache-2.0 - -package cache - -import ( - "encoding/json" - "fmt" - "path/filepath" - "sync" - - "github.com/mandelsoft/logging" - "github.com/mandelsoft/vfs/pkg/osfs" - "github.com/mandelsoft/vfs/pkg/vfs" - - cfgcpi "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - access "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/plugin" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/config" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/internal" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/info" - "github.com/open-component-model/ocm/pkg/errors" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" -) - -var PKG = logging.Package() - -type Cache = *cacheImpl - -var _ config.Target = (*cacheImpl)(nil) - -type cacheImpl struct { - lock sync.RWMutex - - updater cfgcpi.Updater - ctx ocm.Context - plugins map[string]plugin.Plugin - configs map[string]json.RawMessage -} - -func New(ctx ocm.Context, path string) Cache { - c := &cacheImpl{ - ctx: ctx, - plugins: map[string]plugin.Plugin{}, - configs: map[string]json.RawMessage{}, - } - c.updater = cfgcpi.NewUpdater(ctx.ConfigContext(), c) - c.Update() - if path != "" { - c.scan(path) - } - return c -} - -func (c *cacheImpl) Update() { - err := c.updater.Update() - if err != nil { - c.ctx.Logger(PKG).Error("config update failed", "error", err) - } -} - -func (c *cacheImpl) PluginNames() []string { - c.lock.RLock() - defer c.lock.RUnlock() - - return utils.StringMapKeys(c.plugins) -} - -func (c *cacheImpl) GetPlugin(name string) plugin.Plugin { - c.Update() - c.lock.RLock() - defer c.lock.RUnlock() - - p, ok := c.plugins[name] - if ok { - return p - } - return nil -} - -func (c *cacheImpl) add(name string, desc *internal.Descriptor, path string, errmsg string, list *errors.ErrorList) { - c.plugins[name] = plugin.NewPlugin(name, path, c.configs[name], desc, errmsg) - if errmsg != "" && list != nil { - list.Add(fmt.Errorf("%s: %s", name, errmsg)) - } -} - -func (c *cacheImpl) scan(path string) error { - fs := osfs.New() - entries, err := vfs.ReadDir(fs, path) - if err != nil { - return err - } - list := errors.ErrListf("scanning %q", path) - for _, fi := range entries { - if fi.Mode()&0o001 != 0 { - execpath := filepath.Join(path, fi.Name()) - config := c.configs[fi.Name()] - result, err := plugin.Exec(execpath, config, nil, nil, info.NAME) - if err != nil { - c.add(fi.Name(), nil, execpath, err.Error(), list) - continue - } - - // TODO: Version handling by scheme - var desc internal.Descriptor - if err = json.Unmarshal(result, &desc); err != nil { - c.add(fi.Name(), nil, execpath, fmt.Sprintf("cannot unmarshal plugin descriptor: %s", err.Error()), list) - continue - } - - if desc.PluginName != fi.Name() { - c.add(fi.Name(), nil, execpath, fmt.Sprintf("nmatching plugin name %q", desc.PluginName), list) - continue - } - c.add(desc.PluginName, &desc, execpath, "", nil) - } - } - return list.Result() -} - -func (c *cacheImpl) ConfigurePlugin(name string, config json.RawMessage) { - c.lock.Lock() - defer c.lock.Unlock() - - c.configs[name] = config - if p := c.plugins[name]; p != nil { - p.SetConfig(config) - } -} - -// RegisterExtensions registers all the extension provided the found plugin -// at the given context. If no context is given, the cache context is used. -func (c *cacheImpl) RegisterExtensions(ctx ocm.Context) error { - c.lock.RLock() - defer c.lock.RUnlock() - - if ctx == nil { - ctx = c.ctx - } - for _, p := range c.plugins { - if !p.IsValid() { - continue - } - for _, m := range p.GetDescriptor().AccessMethods { - name := m.Name - if m.Version != "" { - name = name + runtime.VersionSeparator + m.Version - } - ctx.AccessMethods().Register(name, access.NewType(name, p, m.Description, m.Format)) - } - } - return nil -} diff --git a/pkg/contexts/ocm/plugin/cache/dircache.go b/pkg/contexts/ocm/plugin/cache/dircache.go new file mode 100644 index 000000000..6c0bb5253 --- /dev/null +++ b/pkg/contexts/ocm/plugin/cache/dircache.go @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package cache + +import ( + "sync" +) + +type PluginDirCache = *pluginDirCache + +type pluginDirCache struct { + lock sync.Mutex + directories map[string]PluginDir + numOfScans int + numOfRequests int +} + +var DirectoryCache = &pluginDirCache{ + directories: map[string]PluginDir{}, +} + +func (c *pluginDirCache) Count() int { + return c.numOfScans +} + +func (c *pluginDirCache) Requests() int { + return c.numOfRequests +} + +func (c *pluginDirCache) Reset() { + c.lock.Lock() + defer c.lock.Unlock() + + c.numOfScans = 0 + c.numOfRequests = 0 + c.directories = map[string]PluginDir{} +} + +func (c *pluginDirCache) Get(path string) PluginDir { + c.lock.Lock() + defer c.lock.Unlock() + + c.numOfRequests++ + found := c.directories[path] + if found == nil { + found = NewDir(path) + c.directories[path] = found + } + return found +} + +func Get(path string) PluginDir { + return DirectoryCache.Get(path) +} diff --git a/pkg/contexts/ocm/plugin/cache/exec.go b/pkg/contexts/ocm/plugin/cache/exec.go new file mode 100644 index 000000000..bb25909f7 --- /dev/null +++ b/pkg/contexts/ocm/plugin/cache/exec.go @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package cache + +import ( + "encoding/json" + "fmt" + "io" + "os/exec" + + "github.com/open-component-model/ocm/pkg/common/accessio" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds" +) + +func Exec(execpath string, config json.RawMessage, r io.Reader, w io.Writer, args ...string) ([]byte, error) { + if len(config) > 0 { + args = append([]string{"-c", string(config)}, args...) + } + cmd := exec.Command(execpath, args...) + stdout := w + if w == nil { + stdout = accessio.LimitBuffer(accessio.DESCRIPTOR_LIMIT) + } + + stderr := accessio.LimitBuffer(accessio.DESCRIPTOR_LIMIT) + + cmd.Stdin = r + cmd.Stdout = stdout + cmd.Stderr = stderr + + err := cmd.Run() + if err != nil { + var result cmds.Error + var msg string + if err := json.Unmarshal(stderr.Bytes(), &result); err == nil { + msg = result.Error + } else { + msg = fmt.Sprintf("[%s]", string(stderr.Bytes())) + } + return nil, fmt.Errorf("%s", msg) + } + if l, ok := stdout.(*accessio.LimitedBuffer); ok { + if l.Exceeded() { + return nil, fmt.Errorf("stdout limit exceeded") + } + return l.Bytes(), nil + } + return nil, nil +} diff --git a/pkg/contexts/ocm/plugin/cache/plugin.go b/pkg/contexts/ocm/plugin/cache/plugin.go new file mode 100644 index 000000000..268e98294 --- /dev/null +++ b/pkg/contexts/ocm/plugin/cache/plugin.go @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package cache + +import ( + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/internal" +) + +type Plugin = *pluginImpl + +// //nolint: errname // is no error. +type pluginImpl struct { + name string + descriptor *internal.Descriptor + path string + error string +} + +func NewPlugin(name string, path string, desc *internal.Descriptor, errmsg string) Plugin { + return &pluginImpl{ + name: name, + path: path, + descriptor: desc, + error: errmsg, + } +} + +func (p *pluginImpl) GetDescriptor() *internal.Descriptor { + return p.descriptor +} + +func (p *pluginImpl) Name() string { + return p.name +} + +func (p *pluginImpl) Path() string { + return p.path +} + +func (p *pluginImpl) Version() string { + if !p.IsValid() { + return "-" + } + return p.descriptor.PluginVersion +} + +func (p *pluginImpl) IsValid() bool { + return p.descriptor != nil +} + +func (p *pluginImpl) Error() string { + return p.error +} + +func (p *pluginImpl) GetAccessMethodDescriptor(name, version string) *internal.AccessMethodDescriptor { + if !p.IsValid() { + return nil + } + + var fallback internal.AccessMethodDescriptor + fallbackFound := false + for _, m := range p.descriptor.AccessMethods { + if m.Name == name { + if m.Version == version { + return &m + } + if m.Version == "" || m.Version == "v1" { + fallback = m + fallbackFound = true + } + } + } + if fallbackFound && (version == "" || version == "v1") { + return &fallback + } + return nil +} + +func (p *pluginImpl) Message() string { + if p.IsValid() { + return p.descriptor.Short + } + if p.error != "" { + return "Error: " + p.error + } + return "unknown state" +} diff --git a/pkg/contexts/ocm/plugin/cache/plugindir.go b/pkg/contexts/ocm/plugin/cache/plugindir.go new file mode 100644 index 000000000..22e333794 --- /dev/null +++ b/pkg/contexts/ocm/plugin/cache/plugindir.go @@ -0,0 +1,97 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package cache + +import ( + "encoding/json" + "fmt" + "path/filepath" + "sync" + + "github.com/mandelsoft/vfs/pkg/osfs" + "github.com/mandelsoft/vfs/pkg/vfs" + + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/internal" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/info" + "github.com/open-component-model/ocm/pkg/errors" + "github.com/open-component-model/ocm/pkg/utils" +) + +type PluginDir = *pluginDirImpl + +type pluginDirImpl struct { + lock sync.RWMutex + + plugins map[string]Plugin +} + +func NewDir(path string) PluginDir { + c := &pluginDirImpl{ + plugins: map[string]Plugin{}, + } + if path != "" { + c.scan(path) + } + return c +} + +func (c *pluginDirImpl) PluginNames() []string { + c.lock.RLock() + defer c.lock.RUnlock() + + return utils.StringMapKeys(c.plugins) +} + +func (c *pluginDirImpl) Get(name string) Plugin { + c.lock.RLock() + defer c.lock.RUnlock() + + p, ok := c.plugins[name] + if ok { + return p + } + return nil +} + +func (c *pluginDirImpl) add(name string, desc *internal.Descriptor, path string, errmsg string, list *errors.ErrorList) { + c.plugins[name] = NewPlugin(name, path, desc, errmsg) + if errmsg != "" && list != nil { + list.Add(fmt.Errorf("%s: %s", name, errmsg)) + } +} + +func (c *pluginDirImpl) scan(path string) error { + DirectoryCache.numOfScans++ + fs := osfs.New() + entries, err := vfs.ReadDir(fs, path) + if err != nil { + return err + } + list := errors.ErrListf("scanning %q", path) + for _, fi := range entries { + if fi.Mode()&0o001 != 0 { + execpath := filepath.Join(path, fi.Name()) + result, err := Exec(execpath, nil, nil, nil, info.NAME) + if err != nil { + c.add(fi.Name(), nil, execpath, err.Error(), list) + continue + } + + // TODO: Version handling by scheme + var desc internal.Descriptor + if err = json.Unmarshal(result, &desc); err != nil { + c.add(fi.Name(), nil, execpath, fmt.Sprintf("cannot unmarshal plugin descriptor: %s", err.Error()), list) + continue + } + + if desc.PluginName != fi.Name() { + c.add(fi.Name(), nil, execpath, fmt.Sprintf("nmatching plugin name %q", desc.PluginName), list) + continue + } + c.add(desc.PluginName, &desc, execpath, "", nil) + } + } + return list.Result() +} diff --git a/pkg/contexts/ocm/plugin/const.go b/pkg/contexts/ocm/plugin/const.go new file mode 100644 index 000000000..c93fc2ec5 --- /dev/null +++ b/pkg/contexts/ocm/plugin/const.go @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package plugin + +import ( + "github.com/mandelsoft/logging" +) + +var PKG = logging.Package() diff --git a/pkg/contexts/ocm/plugin/plugin.go b/pkg/contexts/ocm/plugin/plugin.go index 2cf6e7543..281311a7d 100644 --- a/pkg/contexts/ocm/plugin/plugin.go +++ b/pkg/contexts/ocm/plugin/plugin.go @@ -6,100 +6,36 @@ package plugin import ( "encoding/json" - "fmt" "io" - "os/exec" + "sync" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/internal" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/cache" "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds" "github.com/open-component-model/ocm/pkg/errors" ) type Plugin = *pluginImpl +type impl = cache.Plugin + // //nolint: errname // is no error. type pluginImpl struct { - name string - config json.RawMessage - descriptor *internal.Descriptor - path string - error string + lock sync.RWMutex + impl + config json.RawMessage } -func NewPlugin(name string, path string, config json.RawMessage, desc *Descriptor, errmsg string) Plugin { +func NewPlugin(impl cache.Plugin, config json.RawMessage) Plugin { return &pluginImpl{ - name: name, - path: path, - config: config, - descriptor: desc, - error: errmsg, - } -} - -func (p *pluginImpl) GetDescriptor() *internal.Descriptor { - return p.descriptor -} - -func (p *pluginImpl) Name() string { - return p.name -} - -func (p *pluginImpl) Path() string { - return p.path -} - -func (p *pluginImpl) Version() string { - if !p.IsValid() { - return "-" + impl: impl, + config: config, } - return p.descriptor.PluginVersion -} - -func (p *pluginImpl) IsValid() bool { - return p.descriptor != nil -} - -func (p *pluginImpl) Error() string { - return p.error } -func (p *pluginImpl) SetConfig(data json.RawMessage) { - p.config = data -} - -func (p *pluginImpl) GetAccessMethodDescriptor(name, version string) *internal.AccessMethodDescriptor { - if !p.IsValid() { - return nil - } - - var fallback internal.AccessMethodDescriptor - fallbackFound := false - for _, m := range p.descriptor.AccessMethods { - if m.Name == name { - if m.Version == version { - return &m - } - if m.Version == "" || m.Version == "v1" { - fallback = m - fallbackFound = true - } - } - } - if fallbackFound && (version == "" || version == "v1") { - return &fallback - } - return nil -} - -func (p *pluginImpl) Message() string { - if p.IsValid() { - return p.descriptor.Short - } - if p.error != "" { - return "Error: " + p.error - } - return "unknown state" +func (p *pluginImpl) SetConfig(config json.RawMessage) { + p.lock.Lock() + defer p.lock.Unlock() + p.config = config } func (p *pluginImpl) Validate(spec []byte) (*ppi.AccessSpecInfo, error) { @@ -117,42 +53,5 @@ func (p *pluginImpl) Validate(spec []byte) (*ppi.AccessSpecInfo, error) { } func (p *pluginImpl) Exec(r io.Reader, w io.Writer, args ...string) ([]byte, error) { - return Exec(p.path, p.config, r, w, args...) -} - -func Exec(execpath string, config json.RawMessage, r io.Reader, w io.Writer, args ...string) ([]byte, error) { - if len(config) > 0 { - args = append([]string{"-c", string(config)}, args...) - } - cmd := exec.Command(execpath, args...) - - stdout := w - if w == nil { - stdout = LimitBuffer(LIMIT) - } - - stderr := LimitBuffer(LIMIT) - - cmd.Stdin = r - cmd.Stdout = stdout - cmd.Stderr = stderr - - err := cmd.Run() - if err != nil { - var result cmds.Error - var msg string - if err := json.Unmarshal(stderr.Bytes(), &result); err == nil { - msg = result.Error - } else { - msg = fmt.Sprintf("[%s]", string(stderr.Bytes())) - } - return nil, fmt.Errorf("%s", msg) - } - if l, ok := stdout.(*LimitedBuffer); ok { - if l.Exceeded() { - return nil, fmt.Errorf("stdout limit exceeded") - } - return l.Bytes(), nil - } - return nil, nil + return cache.Exec(p.Path(), p.config, r, w, args...) } diff --git a/pkg/contexts/ocm/plugin/plugin_test.go b/pkg/contexts/ocm/plugin/plugin_test.go index a4b1d86aa..56341f760 100644 --- a/pkg/contexts/ocm/plugin/plugin_test.go +++ b/pkg/contexts/ocm/plugin/plugin_test.go @@ -15,26 +15,41 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/plugindirattr" "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin" "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/cache" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/plugins" ) var _ = Describe("setup plugin cache", func() { var ctx ocm.Context - var registry cache.Cache + var registry plugins.Set BeforeEach(func() { + cache.DirectoryCache.Reset() ctx = ocm.New() plugindirattr.Set(ctx, "testdata") registry = plugincacheattr.Get(ctx) }) It("finds plugin", func() { - p := registry.GetPlugin("test") + p := registry.Get("test") Expect(p).NotTo(BeNil()) Expect(p.GetDescriptor().Short).To(Equal("a test plugin")) }) + It("scans only once", func() { + ctx = ocm.New() + plugindirattr.Set(ctx, "testdata") + registry = plugincacheattr.Get(ctx) + + p := registry.Get("test") + Expect(p).NotTo(BeNil()) + Expect(p.GetDescriptor().Short).To(Equal("a test plugin")) + + Expect(cache.DirectoryCache.Count()).To(Equal(1)) + Expect(cache.DirectoryCache.Requests()).To(Equal(2)) + }) + It("registers access methods", func() { - p := registry.GetPlugin("test") + p := registry.Get("test") Expect(p).NotTo(BeNil()) Expect(len(p.GetDescriptor().AccessMethods)).To(Equal(2)) Expect(registry.RegisterExtensions(nil)).To(Succeed()) diff --git a/pkg/contexts/ocm/plugin/plugins/plugins.go b/pkg/contexts/ocm/plugin/plugins/plugins.go new file mode 100644 index 000000000..0c30b3856 --- /dev/null +++ b/pkg/contexts/ocm/plugin/plugins/plugins.go @@ -0,0 +1,104 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package plugins + +import ( + "encoding/json" + "sync" + + cfgcpi "github.com/open-component-model/ocm/pkg/contexts/config/cpi" + "github.com/open-component-model/ocm/pkg/contexts/ocm" + access "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/plugin" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/cache" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/config" + "github.com/open-component-model/ocm/pkg/runtime" + "github.com/open-component-model/ocm/pkg/utils" +) + +type Set = *pluginsImpl + +type pluginsImpl struct { + lock sync.RWMutex + + updater cfgcpi.Updater + ctx ocm.Context + base cache.PluginDir + configs map[string]json.RawMessage + plugins map[string]plugin.Plugin +} + +var _ config.Target = (*pluginsImpl)(nil) + +func New(ctx ocm.Context, path string) Set { + c := &pluginsImpl{ + ctx: ctx, + configs: map[string]json.RawMessage{}, + plugins: map[string]plugin.Plugin{}, + } + c.updater = cfgcpi.NewUpdater(ctx.ConfigContext(), c) + c.Update() + c.base = cache.Get(path) + for _, n := range c.base.PluginNames() { + c.plugins[n] = plugin.NewPlugin(c.base.Get(n), c.configs[n]) + } + return c +} + +func (c *pluginsImpl) Update() { + err := c.updater.Update() + if err != nil { + c.ctx.Logger(plugin.PKG).Error("config update failed", "error", err) + } +} + +func (c *pluginsImpl) ConfigurePlugin(name string, config json.RawMessage) { + c.lock.Lock() + defer c.lock.Unlock() + + c.configs[name] = config +} + +func (c *pluginsImpl) PluginNames() []string { + c.lock.RLock() + defer c.lock.RUnlock() + + return utils.StringMapKeys(c.plugins) +} + +func (c *pluginsImpl) Get(name string) plugin.Plugin { + c.lock.RLock() + defer c.lock.RUnlock() + + p, ok := c.plugins[name] + if ok { + return p + } + return nil +} + +// RegisterExtensions registers all the extension provided the found plugin +// at the given context. If no context is given, the cache context is used. +func (c *pluginsImpl) RegisterExtensions(ctx ocm.Context) error { + c.lock.RLock() + defer c.lock.RUnlock() + + if ctx == nil { + ctx = c.ctx + } + for _, p := range c.plugins { + if !p.IsValid() { + continue + } + for _, m := range p.GetDescriptor().AccessMethods { + name := m.Name + if m.Version != "" { + name = name + runtime.VersionSeparator + m.Version + } + ctx.AccessMethods().Register(name, access.NewType(name, p, m.Description, m.Format)) + } + } + return nil +} diff --git a/pkg/contexts/ocm/plugin/utils.go b/pkg/contexts/ocm/plugin/utils.go index f99f898b5..7518fdb12 100644 --- a/pkg/contexts/ocm/plugin/utils.go +++ b/pkg/contexts/ocm/plugin/utils.go @@ -5,9 +5,6 @@ package plugin import ( - "bytes" - "io" - "github.com/opencontainers/go-digest" "github.com/open-component-model/ocm/pkg/common/accessio" @@ -15,56 +12,6 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/get" ) -const LIMIT = int64(8196) - -// LimitWriter returns a Writer that writes to w -// but stops with EOF after n bytes. -// The underlying implementation is a *LimitedWriter. -func LimitWriter(w io.Writer, n int64) io.Writer { return &LimitedWriter{w, n} } - -// A LimitedWriter writes to W but limits the amount of -// data written to just N bytes. Each call to Write -// updates N to reflect the new amount remaining. -// Write returns EOF when N <= 0 or when the underlying W returns EOF. -type LimitedWriter struct { - W io.Writer // underlying reader - N int64 // max bytes remaining -} - -func (l *LimitedWriter) Write(p []byte) (n int, err error) { - if l.N <= 0 { - return 0, io.EOF - } - if int64(len(p)) > l.N { - p = p[0:l.N] - } - n, err = l.W.Write(p) - l.N -= int64(n) - return -} - -func LimitBuffer(n int64) *LimitedBuffer { - buf := &LimitedBuffer{max: n} - buf.LimitedWriter = &LimitedWriter{&buf.buffer, n + 1} - return buf -} - -type LimitedBuffer struct { - *LimitedWriter - max int64 - buffer bytes.Buffer -} - -func (b *LimitedBuffer) Exceeded() bool { - return b.LimitedWriter.N > b.max -} - -func (b *LimitedBuffer) Bytes() []byte { - return b.buffer.Bytes() -} - -//////////////////////////////////////////////////////////////////////////////// - type AccessDataWriter struct { plugin Plugin acctype string From 954e3e32d2c09bcf29f9e0d5ade51d9faff8d65a Mon Sep 17 00:00:00 2001 From: Uwe Krueger Date: Wed, 2 Nov 2022 11:32:51 +0100 Subject: [PATCH 4/6] align plugin accessmethod and upload --- cmds/common/const.go | 7 + cmds/demoplugin/accessmethods/demo.go | 5 +- cmds/demoplugin/uploaders/demo.go | 74 +++++- cmds/demoplugin/uploaders/writer.go | 8 +- docs/ocm/plugins.md | 233 ++++++++++++++++++ pkg/contexts/ocm/plugin/interface.go | 5 +- pkg/contexts/ocm/plugin/internal/upload.go | 13 + .../plugin/ppi/cmds/accessmethod/get/cmd.go | 10 +- .../ppi/cmds/accessmethod/validate/cmd.go | 7 +- .../ocm/plugin/ppi/cmds/upload/cmd.go | 82 +----- .../ocm/plugin/ppi/cmds/upload/put/cmd.go | 100 ++++++++ .../plugin/ppi/cmds/upload/validate/cmd.go | 84 +++++++ pkg/contexts/ocm/plugin/ppi/interface.go | 12 +- pkg/contexts/ocm/plugin/ppi/plugin.go | 35 ++- 14 files changed, 573 insertions(+), 102 deletions(-) create mode 100644 cmds/common/const.go create mode 100644 docs/ocm/plugins.md create mode 100644 pkg/contexts/ocm/plugin/internal/upload.go create mode 100644 pkg/contexts/ocm/plugin/ppi/cmds/upload/put/cmd.go create mode 100644 pkg/contexts/ocm/plugin/ppi/cmds/upload/validate/cmd.go diff --git a/cmds/common/const.go b/cmds/common/const.go new file mode 100644 index 000000000..4663991ed --- /dev/null +++ b/cmds/common/const.go @@ -0,0 +1,7 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package common + +const CONSUMER_TYPE = "demo" diff --git a/cmds/demoplugin/accessmethods/demo.go b/cmds/demoplugin/accessmethods/demo.go index ac1036f88..9d9b8accd 100644 --- a/cmds/demoplugin/accessmethods/demo.go +++ b/cmds/demoplugin/accessmethods/demo.go @@ -11,14 +11,13 @@ import ( "strings" "github.com/mandelsoft/filepath/pkg/filepath" + "github.com/open-component-model/ocm/cmds/common" "github.com/open-component-model/ocm/pkg/contexts/credentials" "github.com/open-component-model/ocm/pkg/contexts/oci/identity" "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" "github.com/open-component-model/ocm/pkg/runtime" ) -const CONSUMER_TYPE = "demo" - const NAME = "demo" const VERSION = "v1" @@ -69,7 +68,7 @@ func (a *AccessMethod) ValidateSpecification(p ppi.Plugin, spec ppi.AccessSpec) } info.MediaType = my.MediaType info.ConsumerId = credentials.ConsumerIdentity{ - identity.ID_TYPE: CONSUMER_TYPE, + identity.ID_TYPE: common.CONSUMER_TYPE, identity.ID_HOSTNAME: "localhost", identity.ID_PATHPREFIX: my.Path, } diff --git a/cmds/demoplugin/uploaders/demo.go b/cmds/demoplugin/uploaders/demo.go index 104372d71..1751ea470 100644 --- a/cmds/demoplugin/uploaders/demo.go +++ b/cmds/demoplugin/uploaders/demo.go @@ -5,16 +5,40 @@ package uploaders import ( + "fmt" "io" "os" "path/filepath" + "strings" + "github.com/open-component-model/ocm/cmds/common" "github.com/open-component-model/ocm/pkg/contexts/credentials" + "github.com/open-component-model/ocm/pkg/contexts/oci/identity" "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" + "github.com/open-component-model/ocm/pkg/runtime" "github.com/open-component-model/ocm/cmds/demoplugin/accessmethods" ) +const NAME = "demo" +const VERSION = "v1" + +type TargetSpec struct { + runtime.ObjectVersionedType `json:",inline"` + + Path string `json:"path"` +} + +var types map[string]runtime.TypedObjectDecoder + +func init() { + decoder, err := runtime.NewDirectDecoder(&TargetSpec{}) + if err != nil { + panic(err) + } + types = map[string]runtime.TypedObjectDecoder{NAME + runtime.VersionSeparator + VERSION: decoder} +} + type Uploader struct { ppi.UploaderBase } @@ -27,17 +51,59 @@ func New() ppi.Uploader { } } -func (a *Uploader) Writer(p ppi.Plugin, arttype, mediatype, hint string, creds credentials.Credentials) (io.WriteCloser, ppi.AccessSpecProvider, error) { +func (a *Uploader) Decoders() map[string]runtime.TypedObjectDecoder { + return types +} + +func (a *Uploader) ValidateSpecification(p ppi.Plugin, spec ppi.UploadTargetSpec) (*ppi.UploadTargetSpecInfo, error) { + var info ppi.UploadTargetSpecInfo + my := spec.(*TargetSpec) + + if strings.HasPrefix(my.Path, "/") { + return nil, fmt.Errorf("path must be relative (%s)", my.Path) + } + + info.ConsumerId = credentials.ConsumerIdentity{ + identity.ID_TYPE: common.CONSUMER_TYPE, + identity.ID_HOSTNAME: "localhost", + identity.ID_PATHPREFIX: my.Path, + } + return &info, nil +} + +func (a *Uploader) Writer(p ppi.Plugin, arttype, mediatype, hint string, repo ppi.UploadTargetSpec, creds credentials.Credentials) (io.WriteCloser, ppi.AccessSpecProvider, error) { var file *os.File var err error + + my := repo.(*TargetSpec) + + path := hint + root := os.TempDir() + dir := root + if my.Path != "" { + root = filepath.Join(root, my.Path) + if hint == "" { + path = my.Path + dir = filepath.Join(dir, path) + } else { + path = filepath.Join(my.Path, hint) + dir = filepath.Join(dir, filepath.Dir(path)) + } + } + + err = os.MkdirAll(dir, 0o700) + if err != nil { + return nil, nil, err + } + if hint == "" { - file, err = os.CreateTemp(os.TempDir(), "demo.*.blob") + file, err = os.CreateTemp(root, "demo.*.blob") } else { - file, err = os.OpenFile(filepath.Join(os.TempDir(), hint), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o600) + file, err = os.OpenFile(filepath.Join(os.TempDir(), path), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o600) } if err != nil { return nil, nil, err } - writer := NewWriter(file, mediatype, hint == "", accessmethods.NAME, accessmethods.VERSION) + writer := NewWriter(file, path, mediatype, hint == "", accessmethods.NAME, accessmethods.VERSION) return writer, writer.Specification, nil } diff --git a/cmds/demoplugin/uploaders/writer.go b/cmds/demoplugin/uploaders/writer.go index 9aebcaf4a..f88703973 100644 --- a/cmds/demoplugin/uploaders/writer.go +++ b/cmds/demoplugin/uploaders/writer.go @@ -21,6 +21,7 @@ type writer = accessio.DigestWriter type Writer struct { *writer file *os.File + path string rename bool name string version string @@ -28,10 +29,11 @@ type Writer struct { spec *accessmethods.AccessSpec } -func NewWriter(file *os.File, media string, rename bool, name, version string) *Writer { +func NewWriter(file *os.File, path string, media string, rename bool, name, version string) *Writer { return &Writer{ writer: accessio.NewDefaultDigestWriter(file), file: file, + path: path, rename: rename, name: name, version: version, @@ -42,9 +44,9 @@ func NewWriter(file *os.File, media string, rename bool, name, version string) * func (w *Writer) Close() error { err := w.writer.Close() if err == nil { - n := w.file.Name() + n := w.path if w.rename { - n = filepath.Join(os.TempDir(), common.DigestToFileName(w.writer.Digest())) + n = filepath.Join(os.TempDir(), n, common.DigestToFileName(w.writer.Digest())) err := os.Rename(w.file.Name(), n) if err != nil { return errors.Wrapf(err, "cannot rename %q to %q", w.file.Name(), n) diff --git a/docs/ocm/plugins.md b/docs/ocm/plugins.md new file mode 100644 index 000000000..9d913f457 --- /dev/null +++ b/docs/ocm/plugins.md @@ -0,0 +1,233 @@ +# Extending the Library by Plugins + +The library has several extension points,which can be used by a registration +mechanism to add further variants, like repository types, backend technologies, +access methods, blob downloaders and uploader. + +This requiries Go coding, which is feasable for additional standard +implementations. Nevertheless, it is useful to provide a more dynamic +way to enrich the functionality of the library and the OCM command line +tool. + +This can be achieved by the experimental *plugin* concept. It allows +to implement functionality in separate executables (the plugins) and +register them for any main program based on this library. + +## Commands + +A plugin must provide a set of commands to implement the intended extension. + +The library allows to configure a configuration for a plugin, this configuration +is optionally passed to all commands as JSON argument using option `-c`. + +Errors have to be reported on *stderr* as JSON string with the fields: + +- **`error`** *string* + + The error message provided by a command. + + +### `info` (Plugin Info) + +**Synopsis:** ` [-c ] info` + +The capapilities provided by a plugin are queried using the +command `info`. + +It must reposond with JSON *Plugin Descriptor* on standard output + +#### Plugin Descriptor + +The plugin descriptor describes the capabilities of a plugin. It uses the +following fields: + +- **`version`** *string* + + The format version of the information descriptor. The actually supported + version is `v1` + +- **`pluginName`** *string* + + The name of the plugin, it mist correspond to the file name of the executable. + +- **`pluginVersion`** *string* + + The version of the plugin. This is just an information field not used by the + library + +- **`shortDescription`** *string* + + A short description shown in the plugin overview provided by the commad + `ocm ger plugins`. + +- **`description`** *string* + + A description explaining the capabilities of the plugin + +- **`accessMethods`** *[]AccessMethodDescriptor* + + The list of access methods versions provided by this plugin. + This feature is already used to establish new access types, if + the plugins are registered at an OCM context. + +- **`uploaders`** *[]UploaderDescriptor* **Not yet used** + + The list of supported uploaders. Uploaders will be used in a future + version to describe foreign repository targets for local blobs + of dedicated types imported into an OCM registry. + +#### Access Method Descriptor + +An access method descriptor describes a dedicated supported access method. +It uses the following fields: + +- **`name`** *string* + + The name of the access method. + +- **`version`** *string* + + The version of the access method (default is `v1`. + +- **`description`** *string* + + The description of the dedicated kind of an access method. It must + only be reported for one supported version. + +- **`format`** *string* + + The description of the dedicated format version of an access method. + +#### Uploader Descriptor + +The descriptor for an uploader has the following preliminary fields: + +- **`name`** *string* + + The name of the uploader. + +- **`description`** *string* + + The description of the uploader + +- **`constraints`** *[]Constraint* + + The list of constraints the uploader is usable for. A constraint is described + by two fields: + + - **`artifactType`** *string* + + Restrict the usage to a dedicated artifact type. + + - **`mediaType`** *string* + + Restrict the usage to a dedicated media type of the artifact blob. + +### `accessmethods` (Access Method related Commands)) + +This command group provides all commands used to implement an access method +described by an access method descriptor. It requires the following +nested commands: + +#### `validate` (Validate an Access Specification) + +**Synopsis:** ` [-c ] accessmethod validate ` + +This command accepts an access specification as argument. It is used to +validate the specification and to provide some metadata for the given +specification. + +This meta data has to be provided as JSON string on *stdout* and has the +following fields: + + +- **`mediaType`** *string* + + The media type of the artifact described by the specification. It may be part + of the specification or implicitly determined by the access method. + +- **`description`** *string* + + A short textual description of the described location. + +- **`hint`** *string* + + A name hint of the described location used to recontruct a useful + name for local blobs uploaded to a dedicated repository technology. + +- **`consumerId`** *map[string]string* + + The consumer id used to determine optional credentials for the + underlying repository. If specified, at least the `type` field must be set. + + +#### `get` (Get the Blob described by an Access Specification) + +**Synopsis:** ` [-c ] accessmethod get ` + +**Options**: + +``` + -C, --credential = dedicated credential value (default []) + -c, --credentials YAML credentials +``` + +Return the blob described by the given access method on *stdout* + + +### `upload` (Uploder related Commands)) + +This command group provides all commands used to implement an uploader +described by an uploader descriptor. It requires the following +nested commands: + +#### `validate` (Validate an Upload Target Specification) + +**Synopsis:** ` [-c ] accessmethod validate ` + +**Options:** + + +```go + -a, --artifactType string artifact type of input blob + -m, --mediaType string media type of input blob +``` + +This command accepts a target specification as argument. It is used to +validate the specification and to provide some metadata for the given +specification. + +This meta data has to be provided as JSON string on *stdout* and has the +following fields: + +- **`consumerId`** *map[string]string* + + The consumer id used to determine optional credentials for the + underlying repository. If specified, at least the `type` field must be set. + +#### `put` (Store a Blob in the described Target) + +**Synopsis:** ` [-c ] upload put ` + +**Options**: + +``` + -a, --artifactType string artifact type of input blob + -C, --credential = dedicated credential value (default []) + -c, --credentials YAML credentials + -H, --hint string reference hint for storing blob + -m, --mediaType string media type of input blob +``` + +Read the blob content from *stdin*, store the blob and return the +access specification (as JSON string) usable to retrieve the blob, again, +on * stdout* + +## Implementation support + +This library provides a command frame in package `pkg/contexts/ocm/plugin/ppi`. +It implements all the required command based on some interfaces, which must be +implemented by a plugin. These implementations are registered at a +*Plugin*, which can then be passed to the standard implementation. + +An example can be found in [`cmds/demoplugin`](https://github.com/open-component-model/ocm/tree/main/cmds/demoplugin). \ No newline at end of file diff --git a/pkg/contexts/ocm/plugin/interface.go b/pkg/contexts/ocm/plugin/interface.go index 244436b89..7297301fc 100644 --- a/pkg/contexts/ocm/plugin/interface.go +++ b/pkg/contexts/ocm/plugin/interface.go @@ -9,6 +9,7 @@ import ( ) type ( - Descriptor = internal.Descriptor - AccessSpecInfo = internal.AccessSpecInfo + Descriptor = internal.Descriptor + AccessSpecInfo = internal.AccessSpecInfo + UploadTargetSpecInfo = internal.UploadTargetSpecInfo ) diff --git a/pkg/contexts/ocm/plugin/internal/upload.go b/pkg/contexts/ocm/plugin/internal/upload.go new file mode 100644 index 000000000..31327a501 --- /dev/null +++ b/pkg/contexts/ocm/plugin/internal/upload.go @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package internal + +import ( + "github.com/open-component-model/ocm/pkg/contexts/credentials" +) + +type UploadTargetSpecInfo struct { + ConsumerId credentials.ConsumerIdentity +} diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/get/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/get/cmd.go index a2722c106..044db5c55 100644 --- a/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/get/cmd.go +++ b/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/get/cmd.go @@ -5,6 +5,7 @@ package get import ( + "encoding/json" "fmt" "io" "os" @@ -16,6 +17,7 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/credentials" "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" "github.com/open-component-model/ocm/pkg/errors" + "github.com/open-component-model/ocm/pkg/runtime" ) const Name = "get" @@ -24,7 +26,7 @@ func New(p ppi.Plugin) *cobra.Command { opts := Options{} cmd := &cobra.Command{ - Use: Name + " ", + Use: Name + " [] ", Short: "get blob", Long: "", Args: cobra.ExactArgs(1), @@ -41,7 +43,7 @@ func New(p ppi.Plugin) *cobra.Command { type Options struct { Credentials credentials.DirectCredentials - Specification []byte + Specification json.RawMessage } func (o *Options) AddFlags(fs *pflag.FlagSet) { @@ -50,7 +52,9 @@ func (o *Options) AddFlags(fs *pflag.FlagSet) { } func (o *Options) Complete(args []string) error { - o.Specification = []byte(args[0]) + if err := runtime.DefaultYAMLEncoding.Unmarshal([]byte(args[0]), &o.Specification); err != nil { + return errors.Wrapf(err, "invalid repository specification") + } fmt.Fprintf(os.Stderr, "credentials: %s\n", o.Credentials.String()) return nil diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/validate/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/validate/cmd.go index e9aaeee33..e9d026b78 100644 --- a/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/validate/cmd.go +++ b/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/validate/cmd.go @@ -13,6 +13,7 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/credentials" "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" "github.com/open-component-model/ocm/pkg/errors" + "github.com/open-component-model/ocm/pkg/runtime" ) func New(p ppi.Plugin) *cobra.Command { @@ -35,14 +36,16 @@ func New(p ppi.Plugin) *cobra.Command { } type Options struct { - Specification []byte + Specification json.RawMessage } func (o *Options) AddFlags(fs *pflag.FlagSet) { } func (o *Options) Complete(args []string) error { - o.Specification = []byte(args[0]) + if err := runtime.DefaultYAMLEncoding.Unmarshal([]byte(args[0]), &o.Specification); err != nil { + return errors.Wrapf(err, "invalid access specification") + } return nil } diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/upload/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/upload/cmd.go index 233e23679..800feef15 100644 --- a/pkg/contexts/ocm/plugin/ppi/cmds/upload/cmd.go +++ b/pkg/contexts/ocm/plugin/ppi/cmds/upload/cmd.go @@ -5,89 +5,23 @@ package upload import ( - "encoding/json" - "fmt" - "io" - "os" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - "github.com/open-component-model/ocm/pkg/cobrautils/flag" - "github.com/open-component-model/ocm/pkg/contexts/credentials" "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" - "github.com/open-component-model/ocm/pkg/errors" - "github.com/open-component-model/ocm/pkg/runtime" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/upload/put" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/upload/validate" ) -const Name = "upload" +const NAME = "upload" func New(p ppi.Plugin) *cobra.Command { - opts := Options{} - cmd := &cobra.Command{ - Use: Name + " ", - Short: "upload blob to external repository", + Use: NAME, + Short: "upload specific operations", Long: "", - Args: cobra.ExactArgs(1), - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - return opts.Complete(args) - }, - RunE: func(cmd *cobra.Command, args []string) error { - return Command(p, cmd, &opts) - }, } - opts.AddFlags(cmd.Flags()) - return cmd -} - -type Options struct { - Credentials credentials.DirectCredentials - - MediaType string - ArtifactType string - Hint string - - Repository json.RawMessage -} - -func (o *Options) AddFlags(fs *pflag.FlagSet) { - flag.YAMLVarP(fs, &o.Credentials, "credentials", "c", nil, "credentials") - flag.StringMapVarPA(fs, &o.Credentials, "credential", "C", nil, "dedicated credential value") - fs.StringVarP(&o.MediaType, "mediaType", "m", "", "media type of input blob") - fs.StringVarP(&o.ArtifactType, "artifactType", "a", "", "artifact type of input blob") - fs.StringVarP(&o.Hint, "hint", "H", "", "reference hint for storing blob") -} -func (o *Options) Complete(args []string) error { - if err := runtime.DefaultYAMLEncoding.Unmarshal([]byte(args[0]), o.Repository); err != nil { - return errors.Wrapf(err, "invalid repository specification") - } - return nil -} - -func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error { - u := p.GetUploader(opts.ArtifactType, opts.MediaType) - if u == nil { - return errors.ErrNotFound(ppi.KIND_UPLOADER, fmt.Sprintf("%s:%s", opts.ArtifactType, opts.MediaType)) - } - w, h, err := u.Writer(p, opts.ArtifactType, opts.MediaType, opts.Hint, opts.Credentials) - if err != nil { - return err - } - _, err = io.Copy(w, os.Stdin) - if err != nil { - w.Close() - return err - } - err = w.Close() - if err != nil { - return err - } - spec := h() - data, err := json.Marshal(spec) - if err == nil { - cmd.Printf("%s\n", string(data)) - } - return err + cmd.AddCommand(validate.New(p)) + cmd.AddCommand(put.New(p)) + return cmd } diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/upload/put/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/upload/put/cmd.go new file mode 100644 index 000000000..557f13631 --- /dev/null +++ b/pkg/contexts/ocm/plugin/ppi/cmds/upload/put/cmd.go @@ -0,0 +1,100 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package put + +import ( + "encoding/json" + "fmt" + "io" + "os" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + "github.com/open-component-model/ocm/pkg/cobrautils/flag" + "github.com/open-component-model/ocm/pkg/contexts/credentials" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" + "github.com/open-component-model/ocm/pkg/errors" + "github.com/open-component-model/ocm/pkg/runtime" +) + +const Name = "put" + +func New(p ppi.Plugin) *cobra.Command { + opts := Options{} + + cmd := &cobra.Command{ + Use: Name + " [] ", + Short: "upload blob to external repository", + Long: "", + Args: cobra.ExactArgs(2), + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return opts.Complete(args) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return Command(p, cmd, &opts) + }, + } + opts.AddFlags(cmd.Flags()) + return cmd +} + +type Options struct { + Name string + Specification json.RawMessage + + Credentials credentials.DirectCredentials + MediaType string + ArtifactType string + + Hint string +} + +func (o *Options) AddFlags(fs *pflag.FlagSet) { + flag.YAMLVarP(fs, &o.Credentials, "credentials", "c", nil, "credentials") + flag.StringMapVarPA(fs, &o.Credentials, "credential", "C", nil, "dedicated credential value") + fs.StringVarP(&o.MediaType, "mediaType", "m", "", "media type of input blob") + fs.StringVarP(&o.ArtifactType, "artifactType", "a", "", "artifact type of input blob") + fs.StringVarP(&o.Hint, "hint", "H", "", "reference hint for storing blob") +} + +func (o *Options) Complete(args []string) error { + o.Name = args[0] + if err := runtime.DefaultYAMLEncoding.Unmarshal([]byte(args[1]), &o.Specification); err != nil { + return errors.Wrapf(err, "invalid repository specification") + } + return nil +} + +func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error { + spec, err := p.DecodeUploadTargetSpecification(opts.Specification) + if err != nil { + return err + } + + u := p.GetUploader(opts.Name) + if u == nil { + return errors.ErrNotFound(ppi.KIND_UPLOADER, fmt.Sprintf("%s:%s", opts.ArtifactType, opts.MediaType)) + } + w, h, err := u.Writer(p, opts.ArtifactType, opts.MediaType, opts.Hint, spec, opts.Credentials) + if err != nil { + return err + } + _, err = io.Copy(w, os.Stdin) + if err != nil { + w.Close() + return err + } + err = w.Close() + if err != nil { + return err + } + acc := h() + data, err := json.Marshal(acc) + if err == nil { + cmd.Printf("%s\n", string(data)) + } + return err +} diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/upload/validate/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/upload/validate/cmd.go new file mode 100644 index 000000000..94649b6e4 --- /dev/null +++ b/pkg/contexts/ocm/plugin/ppi/cmds/upload/validate/cmd.go @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package validate + +import ( + "encoding/json" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + "github.com/open-component-model/ocm/pkg/contexts/credentials" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" + "github.com/open-component-model/ocm/pkg/errors" + "github.com/open-component-model/ocm/pkg/runtime" +) + +func New(p ppi.Plugin) *cobra.Command { + opts := Options{} + + cmd := &cobra.Command{ + Use: "validate [flags>] ", + Short: "validate upload specification", + Long: "", + Args: cobra.ExactArgs(2), + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return opts.Complete(args) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return Command(p, cmd, &opts) + }, + } + opts.AddFlags(cmd.Flags()) + return cmd +} + +type Options struct { + Name string + Specification json.RawMessage + + ArtifactType string + MediaType string +} + +func (o *Options) AddFlags(fs *pflag.FlagSet) { + fs.StringVarP(&o.MediaType, "mediaType", "m", "", "media type of input blob") + fs.StringVarP(&o.ArtifactType, "artifactType", "a", "", "artifact type of input blob") +} + +func (o *Options) Complete(args []string) error { + o.Name = args[0] + if err := runtime.DefaultYAMLEncoding.Unmarshal([]byte(args[1]), &o.Specification); err != nil { + return errors.Wrapf(err, "invalid repository specification") + } + return nil +} + +type Result struct { + ConsumerId credentials.ConsumerIdentity `json:"consumerId"` +} + +func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error { + spec, err := p.DecodeUploadTargetSpecification(opts.Specification) + if err != nil { + return err + } + + m := p.GetUploader(opts.Name) + if m == nil { + return errors.ErrUnknown(ppi.KIND_UPLOADER, spec.GetType()) + } + info, err := m.ValidateSpecification(p, spec) + if err != nil { + return err + } + result := Result{info.ConsumerId} + data, err := json.Marshal(result) + if err != nil { + return err + } + cmd.Printf("%s\n", string(data)) + return nil +} diff --git a/pkg/contexts/ocm/plugin/ppi/interface.go b/pkg/contexts/ocm/plugin/ppi/interface.go index 921d08207..59179c73c 100644 --- a/pkg/contexts/ocm/plugin/ppi/interface.go +++ b/pkg/contexts/ocm/plugin/ppi/interface.go @@ -16,6 +16,7 @@ import ( type ( Descriptor = internal.Descriptor AccessSpecInfo = internal.AccessSpecInfo + UploadTargetSpecInfo = internal.UploadTargetSpecInfo UploaderKey = internal.UploaderKey UploaderDescriptor = internal.UploaderDescriptor AccessMethodDescriptor = internal.AccessMethodDescriptor @@ -36,7 +37,9 @@ type Plugin interface { SetLong(s string) RegisterUploader(arttype, mediatype string, u Uploader) error - GetUploader(arttype, mediatype string) Uploader + GetUploader(name string) Uploader + GetUploaderFor(arttype, mediatype string) Uploader + DecodeUploadTargetSpecification(data []byte) (UploadTargetSpec, error) RegisterAccessMethod(m AccessMethod) error DecodeAccessSpecification(data []byte) (AccessSpec, error) @@ -65,8 +68,13 @@ type AccessSpec runtime.VersionedTypedObject type AccessSpecProvider func() AccessSpec type Uploader interface { + Decoders() map[string]runtime.TypedObjectDecoder + Name() string Description() string - Writer(p Plugin, arttyoe, mediatype string, hint string, creds credentials.Credentials) (io.WriteCloser, AccessSpecProvider, error) + ValidateSpecification(p Plugin, spec UploadTargetSpec) (info *UploadTargetSpecInfo, err error) + Writer(p Plugin, arttype, mediatype string, hint string, spec UploadTargetSpec, creds credentials.Credentials) (io.WriteCloser, AccessSpecProvider, error) } + +type UploadTargetSpec runtime.VersionedTypedObject diff --git a/pkg/contexts/ocm/plugin/ppi/plugin.go b/pkg/contexts/ocm/plugin/ppi/plugin.go index 1411f6f68..97165a425 100644 --- a/pkg/contexts/ocm/plugin/ppi/plugin.go +++ b/pkg/contexts/ocm/plugin/ppi/plugin.go @@ -18,8 +18,9 @@ type plugin struct { descriptor internal.Descriptor options Options - uploaders map[string]Uploader - mappings *internal.Registry[Uploader] + uploaders map[string]Uploader + uploaderScheme runtime.Scheme + mappings *internal.Registry[Uploader] methods map[string]AccessMethod accessScheme runtime.Scheme @@ -28,12 +29,13 @@ type plugin struct { func NewPlugin(name string, version string) Plugin { var rt runtime.VersionedTypedObject return &plugin{ - name: name, - version: version, - methods: map[string]AccessMethod{}, - uploaders: map[string]Uploader{}, - mappings: internal.NewRegistry[Uploader](), - accessScheme: runtime.MustNewDefaultScheme(&rt, &runtime.UnstructuredVersionedTypedObject{}, false, nil), + name: name, + version: version, + methods: map[string]AccessMethod{}, + uploaders: map[string]Uploader{}, + mappings: internal.NewRegistry[Uploader](), + accessScheme: runtime.MustNewDefaultScheme(&rt, &runtime.UnstructuredVersionedTypedObject{}, false, nil), + uploaderScheme: runtime.MustNewDefaultScheme(&rt, &runtime.UnstructuredVersionedTypedObject{}, false, nil), descriptor: internal.Descriptor{ Version: internal.VERSION, PluginName: name, @@ -109,14 +111,29 @@ func (p *plugin) RegisterUploader(arttype, mediatype string, u Uploader) error { } d.Costraints = append(d.Costraints, key) } + for n, d := range u.Decoders() { + p.uploaderScheme.RegisterByDecoder(n, d) + } return nil } -func (p *plugin) GetUploader(arttype, mediatype string) Uploader { +func (p *plugin) GetUploader(name string) Uploader { + return p.uploaders[name] +} + +func (p *plugin) GetUploaderFor(arttype, mediatype string) Uploader { u, _ := p.mappings.LookupHandler(arttype, mediatype) return u } +func (p *plugin) DecodeUploadTargetSpecification(data []byte) (UploadTargetSpec, error) { + o, err := p.uploaderScheme.Decode(data, nil) + if err != nil { + return nil, err + } + return o.(UploadTargetSpec), nil +} + func (p *plugin) RegisterAccessMethod(m AccessMethod) error { if p.GetAccessMethod(m.Name(), m.Version()) != nil { n := m.Name() From 52c5d551a33ee211f25083e7fb73fcfd3cbc53b2 Mon Sep 17 00:00:00 2001 From: Uwe Krueger Date: Wed, 2 Nov 2022 19:17:12 +0100 Subject: [PATCH 5/6] fixes + streamline makefile --- Makefile | 31 ++++++---------- cmds/ocm/app/app.go | 2 +- docs/ocm/plugins.md | 13 +++++-- docs/reference/ocm_add_references.md | 2 +- .../ocm_add_resource-configuration.md | 2 +- docs/reference/ocm_add_resources.md | 2 +- .../reference/ocm_add_source-configuration.md | 2 +- docs/reference/ocm_add_sources.md | 2 +- go.mod | 2 +- go.sum | 4 +-- .../ocm/accessmethods/plugin/method_test.go | 2 +- .../ocm/plugin/internal/descriptor.go | 9 +++-- .../ocm/plugin/internal/lookup_test.go | 36 +++++++++---------- pkg/contexts/ocm/plugin/plugin_test.go | 2 +- pkg/contexts/ocm/plugin/plugins/plugins.go | 7 ++-- .../plugin/ppi/cmds/accessmethod/get/cmd.go | 8 ++--- .../ocm/plugin/ppi/cmds/upload/put/cmd.go | 6 ++-- 17 files changed, 66 insertions(+), 66 deletions(-) diff --git a/Makefile b/Makefile index e6b89aeb2..7c422eed9 100644 --- a/Makefile +++ b/Makefile @@ -21,29 +21,18 @@ else GOBIN=$(shell go env GOBIN) endif +NOW := $(shell date --rfc-3339=seconds | sed 's/ /T/') +BUILD_FLAGS := "-s -w \ + -X github.com/open-component-model/ocm/pkg/version.gitVersion=$(EFFECTIVE_VERSION) \ + -X github.com/open-component-model/ocm/pkg/version.gitTreeState=$(GIT_TREE_STATE) \ + -X github.com/open-component-model/ocm/pkg/version.gitCommit=$(COMMIT) \ + -X github.com/open-component-model/ocm/pkg/version.buildDate=$(NOW)" + build: ${SOURCES} mkdir -p bin - go build -ldflags "-s -w \ - -X github.com/open-component-model/ocm/pkg/version.gitVersion=$(EFFECTIVE_VERSION) \ - -X github.com/open-component-model/ocm/pkg/version.gitTreeState=$(GIT_TREE_STATE) \ - -X github.com/open-component-model/ocm/pkg/version.gitCommit=$(COMMIT) \ - -X github.com/open-component-model/ocm/pkg/version.buildDate=$(shell date --rfc-3339=seconds | sed 's/ /T/')" \ - -o bin/ocm \ - ./cmds/ocm - go build -ldflags "-s -w \ - -X github.com/open-component-model/ocm/pkg/version.gitVersion=$(EFFECTIVE_VERSION) \ - -X github.com/open-component-model/ocm/pkg/version.gitTreeState=$(GIT_TREE_STATE) \ - -X github.com/open-component-model/ocm/pkg/version.gitCommit=$(COMMIT) \ - -X github.com/open-component-model/ocm/pkg/version.buildDate=$(shell date --rfc-3339=seconds | sed 's/ /T/')" \ - -o bin/helminstaller \ - ./cmds/helminstaller - go build -ldflags "-s -w \ - -X github.com/open-component-model/ocm/pkg/version.gitVersion=$(EFFECTIVE_VERSION) \ - -X github.com/open-component-model/ocm/pkg/version.gitTreeState=$(GIT_TREE_STATE) \ - -X github.com/open-component-model/ocm/pkg/version.gitCommit=$(COMMIT) \ - -X github.com/open-component-model/ocm/pkg/version.buildDate=$(shell date --rfc-3339=seconds | sed 's/ /T/')" \ - -o bin/demo \ - ./cmds/demoplugin + go build -ldflags $(BUILD_FLAGS) -o bin/ocm ./cmds/ocm + go build -ldflags $(BUILD_FLAGS) -o bin/helminstaller ./cmds/helminstaller + go build -ldflags $(BUILD_FLAGS) -o bin/demo ./cmds/demoplugin .PHONY: install-requirements diff --git a/cmds/ocm/app/app.go b/cmds/ocm/app/app.go index 8147bfebb..6cb2520f5 100644 --- a/cmds/ocm/app/app.go +++ b/cmds/ocm/app/app.go @@ -319,7 +319,7 @@ func (o *CLIOptions) Complete() error { } err = ctx.ApplyConfig(spec, "cli") } - return plugincacheattr.Get(o.Context.OCMContext()).RegisterExtensions(nil) + return plugincacheattr.Get(o.Context.OCMContext()).RegisterExtensions() } func NewVersionCommand(ctx clictx.Context) *cobra.Command { diff --git a/docs/ocm/plugins.md b/docs/ocm/plugins.md index 9d913f457..59bfb987a 100644 --- a/docs/ocm/plugins.md +++ b/docs/ocm/plugins.md @@ -123,6 +123,16 @@ The descriptor for an uploader has the following preliminary fields: Restrict the usage to a dedicated media type of the artifact blob. + - **`contextType`** *string* + + Restrict the usage to a dedicated implementation backend technology. + If specified, the attribute `repositoryType` must be set, also. + + - **`repositoryType`** *string* + + Restrict the usage to a dedicated implementation of the backend technology. + If specified, the attribute `contextType` must be set, also. + ### `accessmethods` (Access Method related Commands)) This command group provides all commands used to implement an access method @@ -140,7 +150,6 @@ specification. This meta data has to be provided as JSON string on *stdout* and has the following fields: - - **`mediaType`** *string* The media type of the artifact described by the specification. It may be part @@ -183,7 +192,7 @@ nested commands: #### `validate` (Validate an Upload Target Specification) -**Synopsis:** ` [-c ] accessmethod validate ` +**Synopsis:** ` [-c ] upload validate ` **Options:** diff --git a/docs/reference/ocm_add_references.md b/docs/reference/ocm_add_references.md index 03bb7fa17..036eed33c 100644 --- a/docs/reference/ocm_add_references.md +++ b/docs/reference/ocm_add_references.md @@ -20,7 +20,7 @@ ocm add references [] { | =} ``` --component string component name - --extra = reference extra identity (default []) + --extra stringToString reference extra identity --label = reference label (leading * indicates signature relevant, optional version separated by @) --name string reference name --reference YAML reference meta data (yaml) diff --git a/docs/reference/ocm_add_resource-configuration.md b/docs/reference/ocm_add_resource-configuration.md index c6f603080..10f5464b4 100644 --- a/docs/reference/ocm_add_resource-configuration.md +++ b/docs/reference/ocm_add_resource-configuration.md @@ -60,7 +60,7 @@ ocm add resource-configuration [] { | == resource extra identity (default []) + --extra stringToString resource extra identity --label = resource label (leading * indicates signature relevant, optional version separated by @) --name string resource name --resource YAML resource meta data (yaml) diff --git a/docs/reference/ocm_add_resources.md b/docs/reference/ocm_add_resources.md index 70fbdb1b6..46e2332ae 100644 --- a/docs/reference/ocm_add_resources.md +++ b/docs/reference/ocm_add_resources.md @@ -60,7 +60,7 @@ ocm add resources [] { | =} ``` --external flag non-local resource - --extra = resource extra identity (default []) + --extra stringToString resource extra identity --label = resource label (leading * indicates signature relevant, optional version separated by @) --name string resource name --resource YAML resource meta data (yaml) diff --git a/docs/reference/ocm_add_source-configuration.md b/docs/reference/ocm_add_source-configuration.md index a726fd134..0d27533b9 100644 --- a/docs/reference/ocm_add_source-configuration.md +++ b/docs/reference/ocm_add_source-configuration.md @@ -59,7 +59,7 @@ ocm add source-configuration [] { | =} #### Source Meta Data Options ``` - --extra = source extra identity (default []) + --extra stringToString source extra identity --label = source label (leading * indicates signature relevant, optional version separated by @) --name string source name --source YAML source meta data (yaml) diff --git a/docs/reference/ocm_add_sources.md b/docs/reference/ocm_add_sources.md index 1e91cd70e..7e2f3659f 100644 --- a/docs/reference/ocm_add_sources.md +++ b/docs/reference/ocm_add_sources.md @@ -59,7 +59,7 @@ ocm add sources [] { | =} #### Source Meta Data Options ``` - --extra = source extra identity (default []) + --extra stringToString source extra identity --label = source label (leading * indicates signature relevant, optional version separated by @) --name string source name --source YAML source meta data (yaml) diff --git a/go.mod b/go.mod index 3a1415ef2..844f2485d 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.18 replace ( github.com/spf13/cobra => github.com/mandelsoft/cobra v1.5.1-0.20221030110806-c236cde1e2bd - github.com/spf13/pflag => github.com/mandelsoft/pflag v0.0.0-20221101220350-bbff7bc4f7b5 + github.com/spf13/pflag => github.com/mandelsoft/pflag v0.0.0-20221103085724-8329072bfc34 ) require ( diff --git a/go.sum b/go.sum index 3b958ecc5..d0de3503e 100644 --- a/go.sum +++ b/go.sum @@ -817,8 +817,8 @@ 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-20221012190501-e17f7961076e h1:vhm0x0KCMwjyrm/WkFXYw68QCWCBVzL1HVnOk0uOJa0= github.com/mandelsoft/logging v0.0.0-20221012190501-e17f7961076e/go.mod h1:vxGpzOesdqLGRSWb4fz5DaH0ekJvkISst4qq6UZ2xFA= -github.com/mandelsoft/pflag v0.0.0-20221101220350-bbff7bc4f7b5 h1:yt0I6oxpTzxAPh+lwUinBiWibbmGXvQJdGzdu0sNMAM= -github.com/mandelsoft/pflag v0.0.0-20221101220350-bbff7bc4f7b5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/mandelsoft/pflag v0.0.0-20221103085724-8329072bfc34 h1:egM66BQkWWFK3CWB2Ydp4eH0EMd+ro9cIErQv8PAN3U= +github.com/mandelsoft/pflag v0.0.0-20221103085724-8329072bfc34/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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= diff --git a/pkg/contexts/ocm/accessmethods/plugin/method_test.go b/pkg/contexts/ocm/accessmethods/plugin/method_test.go index 65ccbce2b..9755ba13e 100644 --- a/pkg/contexts/ocm/accessmethods/plugin/method_test.go +++ b/pkg/contexts/ocm/accessmethods/plugin/method_test.go @@ -45,7 +45,7 @@ someattr: value ctx = env.OCMContext() plugindirattr.Set(ctx, "testdata") registry = plugincacheattr.Get(ctx) - Expect(registry.RegisterExtensions(ctx)).To(Succeed()) + Expect(registry.RegisterExtensions()).To(Succeed()) p := registry.Get("test") Expect(p).NotTo(BeNil()) }) diff --git a/pkg/contexts/ocm/plugin/internal/descriptor.go b/pkg/contexts/ocm/plugin/internal/descriptor.go index 644b9b206..6b3e5ce98 100644 --- a/pkg/contexts/ocm/plugin/internal/descriptor.go +++ b/pkg/contexts/ocm/plugin/internal/descriptor.go @@ -22,11 +22,16 @@ type Descriptor struct { } type UploaderKey struct { - ArtifactType string `json:"artifactType"` - MediaType string `json:"mediaType"` + ContextType string `json:"contextType"` + RepositoryType string `json:"repositoryType"` + ArtifactType string `json:"artifactType"` + MediaType string `json:"mediaType"` } func (k UploaderKey) String() string { + if k.RepositoryType != "" || k.ContextType != "" { + return fmt.Sprintf("%s:%s[%s:%s]", k.ContextType, k.RepositoryType, k.ArtifactType, k.MediaType) + } return fmt.Sprintf("%s:%s", k.ArtifactType, k.MediaType) } diff --git a/pkg/contexts/ocm/plugin/internal/lookup_test.go b/pkg/contexts/ocm/plugin/internal/lookup_test.go index cc5f8e646..a96f51c20 100644 --- a/pkg/contexts/ocm/plugin/internal/lookup_test.go +++ b/pkg/contexts/ocm/plugin/internal/lookup_test.go @@ -19,9 +19,9 @@ var _ = Describe("lookup", func() { }) It("looks up complete", func() { - reg.Register(internal.UploaderKey{"a", "m"}, "test") - reg.Register(internal.UploaderKey{"a", "m1"}, "testm") - reg.Register(internal.UploaderKey{"a1", "m"}, "testa") + reg.Register(internal.UploaderKey{ArtifactType: "a", MediaType: "m"}, "test") + reg.Register(internal.UploaderKey{ArtifactType: "a", MediaType: "m1"}, "testm") + reg.Register(internal.UploaderKey{ArtifactType: "a1", MediaType: "m"}, "testa") h, ok := reg.LookupHandler("a", "m") Expect(ok).To(BeTrue()) @@ -29,9 +29,9 @@ var _ = Describe("lookup", func() { }) It("looks up partial artifact", func() { - reg.Register(internal.UploaderKey{"a", ""}, "test") - reg.Register(internal.UploaderKey{"a", "m1"}, "testm") - reg.Register(internal.UploaderKey{"a1", "m"}, "testa") + reg.Register(internal.UploaderKey{ArtifactType: "a", MediaType: ""}, "test") + reg.Register(internal.UploaderKey{ArtifactType: "a", MediaType: "m1"}, "testm") + reg.Register(internal.UploaderKey{ArtifactType: "a1", MediaType: "m"}, "testa") h, ok := reg.LookupHandler("a", "m") Expect(ok).To(BeTrue()) @@ -39,9 +39,9 @@ var _ = Describe("lookup", func() { }) It("looks up partial media", func() { - reg.Register(internal.UploaderKey{"", "m"}, "test") - reg.Register(internal.UploaderKey{"a", "m1"}, "testm") - reg.Register(internal.UploaderKey{"a1", "m"}, "testa") + reg.Register(internal.UploaderKey{ArtifactType: "", MediaType: "m"}, "test") + reg.Register(internal.UploaderKey{ArtifactType: "a", MediaType: "m1"}, "testm") + reg.Register(internal.UploaderKey{ArtifactType: "a1", MediaType: "m"}, "testa") h, ok := reg.LookupHandler("a", "m") Expect(ok).To(BeTrue()) @@ -49,9 +49,9 @@ var _ = Describe("lookup", func() { }) It("looks complete with media sub type", func() { - reg.Register(internal.UploaderKey{"a", "m"}, "test") - reg.Register(internal.UploaderKey{"a", "m1"}, "testm") - reg.Register(internal.UploaderKey{"a1", "m"}, "testa") + reg.Register(internal.UploaderKey{ArtifactType: "a", MediaType: "m"}, "test") + reg.Register(internal.UploaderKey{ArtifactType: "a", MediaType: "m1"}, "testm") + reg.Register(internal.UploaderKey{ArtifactType: "a1", MediaType: "m"}, "testa") h, ok := reg.LookupHandler("a", "m+tar") Expect(ok).To(BeTrue()) @@ -59,9 +59,9 @@ var _ = Describe("lookup", func() { }) It("looks partial with media sub type", func() { - reg.Register(internal.UploaderKey{"", "m"}, "test") - reg.Register(internal.UploaderKey{"a", "m1"}, "testm") - reg.Register(internal.UploaderKey{"a1", "m"}, "testa") + reg.Register(internal.UploaderKey{ArtifactType: "", MediaType: "m"}, "test") + reg.Register(internal.UploaderKey{ArtifactType: "a", MediaType: "m1"}, "testm") + reg.Register(internal.UploaderKey{ArtifactType: "a1", MediaType: "m"}, "testa") h, ok := reg.LookupHandler("a", "m+tar") Expect(ok).To(BeTrue()) @@ -69,9 +69,9 @@ var _ = Describe("lookup", func() { }) It("prefers art", func() { - reg.Register(internal.UploaderKey{"", "m"}, "testm") - reg.Register(internal.UploaderKey{"a", ""}, "test") - reg.Register(internal.UploaderKey{"a1", "m"}, "testa") + reg.Register(internal.UploaderKey{ArtifactType: "", MediaType: "m"}, "testm") + reg.Register(internal.UploaderKey{ArtifactType: "a", MediaType: ""}, "test") + reg.Register(internal.UploaderKey{ArtifactType: "a1", MediaType: "m"}, "testa") h, ok := reg.LookupHandler("a", "m+tar") Expect(ok).To(BeTrue()) diff --git a/pkg/contexts/ocm/plugin/plugin_test.go b/pkg/contexts/ocm/plugin/plugin_test.go index 56341f760..003c0ef17 100644 --- a/pkg/contexts/ocm/plugin/plugin_test.go +++ b/pkg/contexts/ocm/plugin/plugin_test.go @@ -52,7 +52,7 @@ var _ = Describe("setup plugin cache", func() { p := registry.Get("test") Expect(p).NotTo(BeNil()) Expect(len(p.GetDescriptor().AccessMethods)).To(Equal(2)) - Expect(registry.RegisterExtensions(nil)).To(Succeed()) + Expect(registry.RegisterExtensions()).To(Succeed()) t := ctx.AccessMethods().GetAccessType("test") Expect(t).NotTo(BeNil()) raw := ` diff --git a/pkg/contexts/ocm/plugin/plugins/plugins.go b/pkg/contexts/ocm/plugin/plugins/plugins.go index 0c30b3856..c02c45877 100644 --- a/pkg/contexts/ocm/plugin/plugins/plugins.go +++ b/pkg/contexts/ocm/plugin/plugins/plugins.go @@ -81,13 +81,10 @@ func (c *pluginsImpl) Get(name string) plugin.Plugin { // RegisterExtensions registers all the extension provided the found plugin // at the given context. If no context is given, the cache context is used. -func (c *pluginsImpl) RegisterExtensions(ctx ocm.Context) error { +func (c *pluginsImpl) RegisterExtensions() error { c.lock.RLock() defer c.lock.RUnlock() - if ctx == nil { - ctx = c.ctx - } for _, p := range c.plugins { if !p.IsValid() { continue @@ -97,7 +94,7 @@ func (c *pluginsImpl) RegisterExtensions(ctx ocm.Context) error { if m.Version != "" { name = name + runtime.VersionSeparator + m.Version } - ctx.AccessMethods().Register(name, access.NewType(name, p, m.Description, m.Format)) + c.ctx.AccessMethods().Register(name, access.NewType(name, p, m.Description, m.Format)) } } return nil diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/get/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/get/cmd.go index 044db5c55..4a605f7a8 100644 --- a/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/get/cmd.go +++ b/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/get/cmd.go @@ -42,13 +42,13 @@ func New(p ppi.Plugin) *cobra.Command { } type Options struct { - Credentials credentials.DirectCredentials + Credentials map[string]string Specification json.RawMessage } func (o *Options) AddFlags(fs *pflag.FlagSet) { flag.YAMLVarP(fs, &o.Credentials, "credentials", "c", nil, "credentials") - flag.StringMapVarPA(fs, &o.Credentials, "credential", "C", nil, "dedicated credential value") + fs.StringToStringVarPFA(&o.Credentials, "credential", "C", nil, "dedicated credential value") } func (o *Options) Complete(args []string) error { @@ -56,7 +56,7 @@ func (o *Options) Complete(args []string) error { return errors.Wrapf(err, "invalid repository specification") } - fmt.Fprintf(os.Stderr, "credentials: %s\n", o.Credentials.String()) + fmt.Fprintf(os.Stderr, "credentials: %s\n", credentials.DirectCredentials(o.Credentials).String()) return nil } @@ -74,7 +74,7 @@ func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error { if err != nil { return err } - r, err := m.Reader(p, spec, opts.Credentials) + r, err := m.Reader(p, spec, credentials.DirectCredentials(opts.Credentials)) if err != nil { return err } diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/upload/put/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/upload/put/cmd.go index 557f13631..7cd8f68fb 100644 --- a/pkg/contexts/ocm/plugin/ppi/cmds/upload/put/cmd.go +++ b/pkg/contexts/ocm/plugin/ppi/cmds/upload/put/cmd.go @@ -45,7 +45,7 @@ type Options struct { Name string Specification json.RawMessage - Credentials credentials.DirectCredentials + Credentials map[string]string MediaType string ArtifactType string @@ -54,7 +54,7 @@ type Options struct { func (o *Options) AddFlags(fs *pflag.FlagSet) { flag.YAMLVarP(fs, &o.Credentials, "credentials", "c", nil, "credentials") - flag.StringMapVarPA(fs, &o.Credentials, "credential", "C", nil, "dedicated credential value") + fs.StringToStringVarPF(&o.Credentials, "credential", "C", nil, "dedicated credential value") fs.StringVarP(&o.MediaType, "mediaType", "m", "", "media type of input blob") fs.StringVarP(&o.ArtifactType, "artifactType", "a", "", "artifact type of input blob") fs.StringVarP(&o.Hint, "hint", "H", "", "reference hint for storing blob") @@ -78,7 +78,7 @@ func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error { if u == nil { return errors.ErrNotFound(ppi.KIND_UPLOADER, fmt.Sprintf("%s:%s", opts.ArtifactType, opts.MediaType)) } - w, h, err := u.Writer(p, opts.ArtifactType, opts.MediaType, opts.Hint, spec, opts.Credentials) + w, h, err := u.Writer(p, opts.ArtifactType, opts.MediaType, opts.Hint, spec, credentials.DirectCredentials(opts.Credentials)) if err != nil { return err } From efc3c46980e9b121af6957f8c73b152bc4384462 Mon Sep 17 00:00:00 2001 From: Uwe Krueger Date: Thu, 3 Nov 2022 12:20:42 +0100 Subject: [PATCH 6/6] adapt string-to-string flag --- docs/reference/ocm_add_references.md | 2 +- .../ocm_add_resource-configuration.md | 2 +- docs/reference/ocm_add_resources.md | 2 +- .../reference/ocm_add_source-configuration.md | 2 +- docs/reference/ocm_add_sources.md | 2 +- go.mod | 2 +- go.sum | 4 +- pkg/cobrautils/flag/string_to_string.go | 109 +++++++++++++ pkg/cobrautils/flag/string_to_string_test.go | 154 ++++++++++++++++++ pkg/cobrautils/flagsets/types.go | 2 +- .../plugin/ppi/cmds/accessmethod/get/cmd.go | 8 +- .../ocm/plugin/ppi/cmds/upload/put/cmd.go | 6 +- 12 files changed, 279 insertions(+), 16 deletions(-) create mode 100644 pkg/cobrautils/flag/string_to_string.go create mode 100644 pkg/cobrautils/flag/string_to_string_test.go diff --git a/docs/reference/ocm_add_references.md b/docs/reference/ocm_add_references.md index 036eed33c..edf4f2be8 100644 --- a/docs/reference/ocm_add_references.md +++ b/docs/reference/ocm_add_references.md @@ -20,7 +20,7 @@ ocm add references [] { | =} ``` --component string component name - --extra stringToString reference extra identity + --extra = reference extra identity --label = reference label (leading * indicates signature relevant, optional version separated by @) --name string reference name --reference YAML reference meta data (yaml) diff --git a/docs/reference/ocm_add_resource-configuration.md b/docs/reference/ocm_add_resource-configuration.md index 10f5464b4..a328eb4d4 100644 --- a/docs/reference/ocm_add_resource-configuration.md +++ b/docs/reference/ocm_add_resource-configuration.md @@ -60,7 +60,7 @@ ocm add resource-configuration [] { | == resource extra identity --label = resource label (leading * indicates signature relevant, optional version separated by @) --name string resource name --resource YAML resource meta data (yaml) diff --git a/docs/reference/ocm_add_resources.md b/docs/reference/ocm_add_resources.md index 46e2332ae..91f7bf48d 100644 --- a/docs/reference/ocm_add_resources.md +++ b/docs/reference/ocm_add_resources.md @@ -60,7 +60,7 @@ ocm add resources [] { | =} ``` --external flag non-local resource - --extra stringToString resource extra identity + --extra = resource extra identity --label = resource label (leading * indicates signature relevant, optional version separated by @) --name string resource name --resource YAML resource meta data (yaml) diff --git a/docs/reference/ocm_add_source-configuration.md b/docs/reference/ocm_add_source-configuration.md index 0d27533b9..21f27af8e 100644 --- a/docs/reference/ocm_add_source-configuration.md +++ b/docs/reference/ocm_add_source-configuration.md @@ -59,7 +59,7 @@ ocm add source-configuration [] { | =} #### Source Meta Data Options ``` - --extra stringToString source extra identity + --extra = source extra identity --label = source label (leading * indicates signature relevant, optional version separated by @) --name string source name --source YAML source meta data (yaml) diff --git a/docs/reference/ocm_add_sources.md b/docs/reference/ocm_add_sources.md index 7e2f3659f..d6432f327 100644 --- a/docs/reference/ocm_add_sources.md +++ b/docs/reference/ocm_add_sources.md @@ -59,7 +59,7 @@ ocm add sources [] { | =} #### Source Meta Data Options ``` - --extra stringToString source extra identity + --extra = source extra identity --label = source label (leading * indicates signature relevant, optional version separated by @) --name string source name --source YAML source meta data (yaml) diff --git a/go.mod b/go.mod index 844f2485d..4ba27d7ef 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.18 replace ( github.com/spf13/cobra => github.com/mandelsoft/cobra v1.5.1-0.20221030110806-c236cde1e2bd - github.com/spf13/pflag => github.com/mandelsoft/pflag v0.0.0-20221103085724-8329072bfc34 + github.com/spf13/pflag => github.com/mandelsoft/pflag v0.0.0-20221103113840-a15b5f7fa89e ) require ( diff --git a/go.sum b/go.sum index d0de3503e..4701b28c5 100644 --- a/go.sum +++ b/go.sum @@ -817,8 +817,8 @@ 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-20221012190501-e17f7961076e h1:vhm0x0KCMwjyrm/WkFXYw68QCWCBVzL1HVnOk0uOJa0= github.com/mandelsoft/logging v0.0.0-20221012190501-e17f7961076e/go.mod h1:vxGpzOesdqLGRSWb4fz5DaH0ekJvkISst4qq6UZ2xFA= -github.com/mandelsoft/pflag v0.0.0-20221103085724-8329072bfc34 h1:egM66BQkWWFK3CWB2Ydp4eH0EMd+ro9cIErQv8PAN3U= -github.com/mandelsoft/pflag v0.0.0-20221103085724-8329072bfc34/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/mandelsoft/pflag v0.0.0-20221103113840-a15b5f7fa89e h1:XW05FCl7x4ZLYL109cCqZbyF7vq5ihmOWIleT+luIr4= +github.com/mandelsoft/pflag v0.0.0-20221103113840-a15b5f7fa89e/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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= diff --git a/pkg/cobrautils/flag/string_to_string.go b/pkg/cobrautils/flag/string_to_string.go new file mode 100644 index 000000000..43ed78555 --- /dev/null +++ b/pkg/cobrautils/flag/string_to_string.go @@ -0,0 +1,109 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of ths2s source code s2s governed by a BSD-style +// license that can be found in the github.com/spf13/pflag LICENSE file. + +// taken from github.com/spf13/pflag and adapted to support +// any string map types by using generics. + +package flag + +import ( + "bytes" + "encoding/csv" + "fmt" + "strings" + + "github.com/spf13/pflag" +) + +type stringToStringValue[T ~map[string]string] struct { + value *T + changed bool +} + +func newStringToStringValue[T ~map[string]string](val map[string]string, p *T) *stringToStringValue[T] { + ssv := new(stringToStringValue[T]) + ssv.value = p + *ssv.value = val + return ssv +} + +// Set Format: a=1,b=2. +func (s *stringToStringValue[T]) Set(val string) error { + var ss []string + n := strings.Count(val, "=") + switch n { + case 0: + return fmt.Errorf("%s must be formatted as key=value", val) + case 1: + ss = append(ss, strings.Trim(val, `"`)) + default: + r := csv.NewReader(strings.NewReader(val)) + var err error + ss, err = r.Read() + if err != nil { + return err + } + } + + out := make(map[string]string, len(ss)) + for _, pair := range ss { + kv := strings.SplitN(pair, "=", 2) + if len(kv) != 2 { + return fmt.Errorf("%s must be formatted as key=value", pair) + } + out[kv[0]] = kv[1] + } + if !s.changed { + *s.value = out + } else { + for k, v := range out { + (*s.value)[k] = v + } + } + s.changed = true + return nil +} + +func (s *stringToStringValue[T]) Type() string { + return "=" +} + +func (s *stringToStringValue[T]) String() string { + records := make([]string, 0, len(*s.value)>>1) + for k, v := range *s.value { + records = append(records, k+"="+v) + } + + var buf bytes.Buffer + w := csv.NewWriter(&buf) + if err := w.Write(records); err != nil { + panic(err) + } + w.Flush() + return "[" + strings.TrimSpace(buf.String()) + "]" +} + +// StringToStringVar defines a string flag with specified name, default value, and usage string. +// The argument p points to a map[string]string variable in which to store the values of the multiple flags. +// The value of each argument will not try to be separated by comma. +func StringToStringVar[T ~map[string]string](f *pflag.FlagSet, p *T, name string, value map[string]string, usage string) { + f.VarP(newStringToStringValue(value, p), name, "", usage) +} + +// StringToStringVarP is like StringToStringVar, but accepts a shorthand letter that can be used after a single dash. +func StringToStringVarP[T ~map[string]string](f *pflag.FlagSet, p *T, name, shorthand string, value map[string]string, usage string) { + f.VarP(newStringToStringValue(value, p), name, shorthand, usage) +} + +// StringToStringVarPF is like StringToStringVarP, but returns the created flag. +func StringToStringVarPF[T ~map[string]string](f *pflag.FlagSet, p *T, name, shorthand string, value map[string]string, usage string) *pflag.Flag { + return f.VarPF(newStringToStringValue(value, p), name, shorthand, usage) +} + +// StringToStringVarPFA is like StringToStringVarPF, but allows to add to a preset map. +func StringToStringVarPFA[T ~map[string]string](f *pflag.FlagSet, p *T, name, shorthand string, value map[string]string, usage string) *pflag.Flag { + v := newStringToStringValue(value, p) + v.changed = true + return f.VarPF(v, name, shorthand, usage) +} diff --git a/pkg/cobrautils/flag/string_to_string_test.go b/pkg/cobrautils/flag/string_to_string_test.go new file mode 100644 index 000000000..322ac0ae8 --- /dev/null +++ b/pkg/cobrautils/flag/string_to_string_test.go @@ -0,0 +1,154 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of ths2s source code s2s governed by a BSD-style +// license that can be found in the github.com/spf13/pflag LICENSE file. + +package flag_test + +import ( + "bytes" + "encoding/csv" + "fmt" + "strings" + "testing" + + "github.com/spf13/pflag" + + "github.com/open-component-model/ocm/pkg/cobrautils/flag" +) + +type ( + Flag = pflag.Flag + FlagSet = pflag.FlagSet +) + +const ContinueOnError = pflag.ContinueOnError + +var NewFlagSet = pflag.NewFlagSet + +type MyMap map[string]string + +func setUpS2SFlagSet(s2sp *MyMap) *FlagSet { + f := NewFlagSet("test", ContinueOnError) + flag.StringToStringVar(f, s2sp, "s2s", map[string]string{}, "Command separated ls2st!") + return f +} + +func setUpS2SFlagSetWithDefault(s2sp *MyMap) *FlagSet { + f := NewFlagSet("test", ContinueOnError) + flag.StringToStringVar(f, s2sp, "s2s", map[string]string{"da": "1", "db": "2", "de": "5,6"}, "Command separated ls2st!") + return f +} + +func createS2SFlag(vals map[string]string) string { + records := make([]string, 0, len(vals)>>1) + for k, v := range vals { + records = append(records, k+"="+v) + } + + var buf bytes.Buffer + w := csv.NewWriter(&buf) + if err := w.Write(records); err != nil { + panic(err) + } + w.Flush() + return strings.TrimSpace(buf.String()) +} + +func TestEmptyS2S(t *testing.T) { + var s2s MyMap + f := setUpS2SFlagSet(&s2s) + err := f.Parse([]string{}) + if err != nil { + t.Fatal("expected no error; got", err) + } + + if len(s2s) != 0 { + t.Fatalf("got s2s %v with len=%d but expected length=0", s2s, len(s2s)) + } +} + +func TestS2S(t *testing.T) { + var s2s MyMap + f := setUpS2SFlagSet(&s2s) + + vals := map[string]string{"a": "1", "b": "2", "d": "4", "c": "3", "e": "5,6"} + arg := fmt.Sprintf("--s2s=%s", createS2SFlag(vals)) + err := f.Parse([]string{arg}) + if err != nil { + t.Fatal("expected no error; got", err) + } + for k, v := range s2s { + if vals[k] != v { + t.Fatalf("expected s2s[%s] to be %s but got: %s", k, vals[k], v) + } + } +} + +func TestS2SDefault(t *testing.T) { + var s2s MyMap + f := setUpS2SFlagSetWithDefault(&s2s) + + vals := map[string]string{"da": "1", "db": "2", "de": "5,6"} + + err := f.Parse([]string{}) + if err != nil { + t.Fatal("expected no error; got", err) + } + for k, v := range s2s { + if vals[k] != v { + t.Fatalf("expected s2s[%s] to be %s but got: %s", k, vals[k], v) + } + } +} + +func TestS2SWithDefault(t *testing.T) { + var s2s MyMap + f := setUpS2SFlagSetWithDefault(&s2s) + + vals := map[string]string{"a": "1", "b": "2", "e": "5,6"} + arg := fmt.Sprintf("--s2s=%s", createS2SFlag(vals)) + err := f.Parse([]string{arg}) + if err != nil { + t.Fatal("expected no error; got", err) + } + for k, v := range s2s { + if vals[k] != v { + t.Fatalf("expected s2s[%s] to be %s but got: %s", k, vals[k], v) + } + } + + flag := f.Lookup("s2s") + if flag == nil { + t.Fatal("flag \"s2s\" not found") + } + for k, v := range s2s { + if vals[k] != v { + t.Fatalf("expected s2s[%s] to be %s, but got: %s", k, vals[k], v) + } + } +} + +func TestS2SCalledTwice(t *testing.T) { + var s2s MyMap + f := setUpS2SFlagSet(&s2s) + + in := []string{"a=1,b=2", "b=3", `"e=5,6"`, `f=7,8`} + expected := map[string]string{"a": "1", "b": "3", "e": "5,6", "f": "7,8"} + argfmt := "--s2s=%s" + arg0 := fmt.Sprintf(argfmt, in[0]) + arg1 := fmt.Sprintf(argfmt, in[1]) + arg2 := fmt.Sprintf(argfmt, in[2]) + arg3 := fmt.Sprintf(argfmt, in[3]) + err := f.Parse([]string{arg0, arg1, arg2, arg3}) + if err != nil { + t.Fatal("expected no error; got", err) + } + if len(s2s) != len(expected) { + t.Fatalf("expected %d flags; got %d flags", len(expected), len(s2s)) + } + for i, v := range s2s { + if expected[i] != v { + t.Fatalf("expected s2s[%s] to be %s but got: %s", i, expected[i], v) + } + } +} diff --git a/pkg/cobrautils/flagsets/types.go b/pkg/cobrautils/flagsets/types.go index f865f4f94..7a660daba 100644 --- a/pkg/cobrautils/flagsets/types.go +++ b/pkg/cobrautils/flagsets/types.go @@ -354,7 +354,7 @@ type StringMapOption struct { var _ Option = (*StringMapOption)(nil) func (o *StringMapOption) AddFlags(fs *pflag.FlagSet) { - o.TweakFlag(fs.StringToStringVarPF(&o.value, o.otyp.Name(), "", nil, o.otyp.Description())) + o.TweakFlag(flag.StringToStringVarPF(fs, &o.value, o.otyp.Name(), "", nil, o.otyp.Description())) } func (o *StringMapOption) Value() interface{} { diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/get/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/get/cmd.go index 4a605f7a8..5d8009984 100644 --- a/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/get/cmd.go +++ b/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/get/cmd.go @@ -42,13 +42,13 @@ func New(p ppi.Plugin) *cobra.Command { } type Options struct { - Credentials map[string]string + Credentials credentials.DirectCredentials Specification json.RawMessage } func (o *Options) AddFlags(fs *pflag.FlagSet) { flag.YAMLVarP(fs, &o.Credentials, "credentials", "c", nil, "credentials") - fs.StringToStringVarPFA(&o.Credentials, "credential", "C", nil, "dedicated credential value") + flag.StringToStringVarPFA(fs, &o.Credentials, "credential", "C", nil, "dedicated credential value") } func (o *Options) Complete(args []string) error { @@ -56,7 +56,7 @@ func (o *Options) Complete(args []string) error { return errors.Wrapf(err, "invalid repository specification") } - fmt.Fprintf(os.Stderr, "credentials: %s\n", credentials.DirectCredentials(o.Credentials).String()) + fmt.Fprintf(os.Stderr, "credentials: %s\n", o.Credentials.String()) return nil } @@ -74,7 +74,7 @@ func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error { if err != nil { return err } - r, err := m.Reader(p, spec, credentials.DirectCredentials(opts.Credentials)) + r, err := m.Reader(p, spec, opts.Credentials) if err != nil { return err } diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/upload/put/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/upload/put/cmd.go index 7cd8f68fb..457ce9210 100644 --- a/pkg/contexts/ocm/plugin/ppi/cmds/upload/put/cmd.go +++ b/pkg/contexts/ocm/plugin/ppi/cmds/upload/put/cmd.go @@ -45,7 +45,7 @@ type Options struct { Name string Specification json.RawMessage - Credentials map[string]string + Credentials credentials.DirectCredentials MediaType string ArtifactType string @@ -54,7 +54,7 @@ type Options struct { func (o *Options) AddFlags(fs *pflag.FlagSet) { flag.YAMLVarP(fs, &o.Credentials, "credentials", "c", nil, "credentials") - fs.StringToStringVarPF(&o.Credentials, "credential", "C", nil, "dedicated credential value") + flag.StringToStringVarPF(fs, &o.Credentials, "credential", "C", nil, "dedicated credential value") fs.StringVarP(&o.MediaType, "mediaType", "m", "", "media type of input blob") fs.StringVarP(&o.ArtifactType, "artifactType", "a", "", "artifact type of input blob") fs.StringVarP(&o.Hint, "hint", "H", "", "reference hint for storing blob") @@ -78,7 +78,7 @@ func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error { if u == nil { return errors.ErrNotFound(ppi.KIND_UPLOADER, fmt.Sprintf("%s:%s", opts.ArtifactType, opts.MediaType)) } - w, h, err := u.Writer(p, opts.ArtifactType, opts.MediaType, opts.Hint, spec, credentials.DirectCredentials(opts.Credentials)) + w, h, err := u.Writer(p, opts.ArtifactType, opts.MediaType, opts.Hint, spec, opts.Credentials) if err != nil { return err }