Skip to content

Commit

Permalink
prepare input plugin support
Browse files Browse the repository at this point in the history
  • Loading branch information
mandelsoft committed Dec 9, 2024
1 parent 0625bc9 commit 5dd5512
Show file tree
Hide file tree
Showing 13 changed files with 552 additions and 21 deletions.
1 change: 1 addition & 0 deletions api/ocm/plugin/descriptor/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const (
KIND_PLUGIN = "plugin"
KIND_DOWNLOADER = "downloader"
KIND_UPLOADER = "uploader"
KIND_INPUTTYPE = "input type"
KIND_ACCESSMETHOD = errkind.KIND_ACCESSMETHOD
KIND_ACTION = action.KIND_ACTION
KIND_TRANSFERHANDLER = "transferhandler"
Expand Down
9 changes: 9 additions & 0 deletions api/ocm/plugin/descriptor/descriptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type Descriptor struct {
ForwardLogging bool `json:"forwardLogging"`

Actions []ActionDescriptor `json:"actions,omitempty"`
Inputs []InputTypeDescriptor `json:"inputs,omitempty"`
AccessMethods []AccessMethodDescriptor `json:"accessMethods,omitempty"`
Uploaders List[UploaderDescriptor] `json:"uploaders,omitempty"`
Downloaders List[DownloaderDescriptor] `json:"downloaders,omitempty"`
Expand Down Expand Up @@ -125,12 +126,20 @@ func (d UploaderDescriptor) GetConstraints() []UploaderKey {
return d.Constraints
}

////////////////////////////////////////////////////////////////////////////////

type AccessMethodDescriptor struct {
ValueSetDefinition `json:",inline"`
}

////////////////////////////////////////////////////////////////////////////////

type InputTypeDescriptor struct {
ValueSetDefinition `json:",inline"`
}

////////////////////////////////////////////////////////////////////////////////

type ValueTypeDefinition struct {
Name string `json:"name"`
Version string `json:"version,omitempty"`
Expand Down
3 changes: 3 additions & 0 deletions api/ocm/plugin/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const (
KIND_PLUGIN = descriptor.KIND_PLUGIN
KIND_UPLOADER = descriptor.KIND_UPLOADER
KIND_ACCESSMETHOD = descriptor.KIND_ACCESSMETHOD
KIND_INPUTTYÜE = descriptor.KIND_INPUTTYPE
KIND_ACTION = descriptor.KIND_ACTION
KIND_TRANSFERHANDLER = descriptor.KIND_TRANSFERHANDLER
)
Expand All @@ -20,6 +21,7 @@ type (
Descriptor = descriptor.Descriptor
ActionDescriptor = descriptor.ActionDescriptor
ValueMergeHandlerDescriptor = descriptor.ValueMergeHandlerDescriptor
InputTypeDescriptor = descriptor.InputTypeDescriptor
AccessMethodDescriptor = descriptor.AccessMethodDescriptor
DownloaderDescriptor = descriptor.DownloaderDescriptor
DownloaderKey = descriptor.DownloaderKey
Expand All @@ -31,6 +33,7 @@ type (
CommandDescriptor = descriptor.CommandDescriptor

AccessSpecInfo = internal.AccessSpecInfo
InputSpecInfo = internal.InputSpecInfo
UploadTargetSpecInfo = internal.UploadTargetSpecInfo

SignatureSpec = internal.SignatureSpec
Expand Down
12 changes: 12 additions & 0 deletions api/ocm/plugin/internal/input.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package internal

import (
"ocm.software/ocm/api/credentials"
)

type InputSpecInfo struct {
Short string `json:"short"`
MediaType string `json:"mediaType"`
Hint string `json:"hint"`
ConsumerId credentials.ConsumerIdentity `json:"consumerId"`
}
71 changes: 71 additions & 0 deletions api/ocm/plugin/plugin_input.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package plugin

import (
"encoding/json"
"io"

"github.com/mandelsoft/goutils/errors"

"ocm.software/ocm/api/ocm/plugin/ppi"
"ocm.software/ocm/api/ocm/plugin/ppi/cmds/accessmethod"
"ocm.software/ocm/api/ocm/plugin/ppi/cmds/input"
"ocm.software/ocm/api/ocm/plugin/ppi/cmds/input/compose"
"ocm.software/ocm/api/ocm/plugin/ppi/cmds/input/get"
"ocm.software/ocm/api/ocm/plugin/ppi/cmds/input/validate"
"ocm.software/ocm/api/utils/cobrautils/flagsets"
)

func (p *pluginImpl) ValidateInputSpec(spec []byte) (*ppi.InputSpecInfo, error) {
result, err := p.Exec(nil, nil, input.Name, validate.Name, string(spec))
if err != nil {
return nil, errors.Wrapf(err, "plugin %s", p.Name())
}

var info ppi.InputSpecInfo
err = json.Unmarshal(result, &info)
if err != nil {
return nil, errors.Wrapf(err, "plugin %s: cannot unmarshal input spec info", p.Name())
}
return &info, nil
}

func (p *pluginImpl) ComposeInputSpec(name string, opts flagsets.ConfigOptions, base flagsets.Config) error {
cfg := flagsets.Config{}
for _, o := range opts.Options() {
cfg[o.GetName()] = o.Value()
}
optsdata, err := json.Marshal(cfg)
if err != nil {
return errors.Wrapf(err, "cannot marshal option values")
}
basedata, err := json.Marshal(base)
if err != nil {
return errors.Wrapf(err, "cannot marshal input specification base value")
}
result, err := p.Exec(nil, nil, input.Name, compose.Name, name, string(optsdata), string(basedata))
if err != nil {
return err
}
var r flagsets.Config
err = json.Unmarshal(result, &r)
if err != nil {
return errors.Wrapf(err, "cannot unmarshal composition result")
}

for k := range base {
delete(base, k)
}
for k, v := range r {
base[k] = v
}
return nil
}

func (p *pluginImpl) GetInputBlob(w io.Writer, creds, spec json.RawMessage) error {
args := []string{accessmethod.Name, get.Name, string(spec)}
if creds != nil {
args = append(args, "--"+get.OptCreds, string(creds))
}
_, err := p.Exec(nil, w, args...)
return err
}
2 changes: 2 additions & 0 deletions api/ocm/plugin/ppi/cmds/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"ocm.software/ocm/api/ocm/plugin/ppi/cmds/describe"
"ocm.software/ocm/api/ocm/plugin/ppi/cmds/download"
"ocm.software/ocm/api/ocm/plugin/ppi/cmds/info"
"ocm.software/ocm/api/ocm/plugin/ppi/cmds/input"
"ocm.software/ocm/api/ocm/plugin/ppi/cmds/mergehandler"
"ocm.software/ocm/api/ocm/plugin/ppi/cmds/signing"
"ocm.software/ocm/api/ocm/plugin/ppi/cmds/topics/descriptor"
Expand Down Expand Up @@ -65,6 +66,7 @@ func NewPluginCommand(p ppi.Plugin, opts ...Option) *PluginCommand {
cmd.AddCommand(action.New(p))
cmd.AddCommand(mergehandler.New(p))
cmd.AddCommand(accessmethod.New(p))
cmd.AddCommand(input.New(p))
cmd.AddCommand(upload.New(p))
cmd.AddCommand(download.New(p))
cmd.AddCommand(valueset.New(p))
Expand Down
26 changes: 26 additions & 0 deletions api/ocm/plugin/ppi/cmds/input/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package input

import (
"github.com/spf13/cobra"

"ocm.software/ocm/api/ocm/plugin/ppi"
"ocm.software/ocm/api/ocm/plugin/ppi/cmds/input/compose"
"ocm.software/ocm/api/ocm/plugin/ppi/cmds/input/get"
"ocm.software/ocm/api/ocm/plugin/ppi/cmds/input/validate"
)

const Name = "input"

func New(p ppi.Plugin) *cobra.Command {
cmd := &cobra.Command{
Use: Name,
Short: "input operations for component version composition",
Long: `This command group provides all commands used to implement an input type
described by an input type descriptor (<CMD>` + p.Name() + ` descriptor</CMD>.`,
}

cmd.AddCommand(validate.New(p))
cmd.AddCommand(get.New(p))
cmd.AddCommand(compose.New(p))
return cmd
}
93 changes: 93 additions & 0 deletions api/ocm/plugin/ppi/cmds/input/compose/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package compose

import (
"encoding/json"

"github.com/mandelsoft/goutils/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"ocm.software/ocm/api/ocm/plugin/descriptor"

"ocm.software/ocm/api/ocm/extensions/accessmethods/options"
"ocm.software/ocm/api/ocm/plugin/ppi"
"ocm.software/ocm/api/utils/runtime"
)

const Name = "compose"

func New(p ppi.Plugin) *cobra.Command {
opts := Options{}

cmd := &cobra.Command{
Use: Name + " <name> <options json> <base spec json>",
Short: "compose input specification from options and base specification",
Long: `
The task of this command is to compose an input specification based on some
explicitly given input options and preconfigured specifications.
The finally composed input specification has to be returned as JSON document
on *stdout*.
This command is only used, if for an input method descriptor configuration
options are defined (<CMD>` + p.Name() + ` descriptor</CMD>).
If possible, predefined standard options should be used. In such a case only the
<code>name</code> field should be defined for an option. If required, new options can be
defined by additionally specifying a type and a description. New options should
be used very carefully. The chosen names MUST not conflict with names provided
by other plugins. Therefore, it is highly recommended to use names prefixed
by the plugin name.
` + options.DefaultRegistry.Usage(),
Args: cobra.ExactArgs(3),
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
return opts.Complete(args)
},
RunE: func(cmd *cobra.Command, args []string) error {
return Command(p, cmd, &opts)
},
}
opts.AddFlags(cmd.Flags())
return cmd
}

type Options struct {
Name string
Options ppi.Config
Base ppi.Config
}

func (o *Options) AddFlags(fs *pflag.FlagSet) {
}

func (o *Options) Complete(args []string) error {
o.Name = args[0]
if err := runtime.DefaultYAMLEncoding.Unmarshal([]byte(args[1]), &o.Options); err != nil {
return errors.Wrapf(err, "invalid input specification options")
}
if err := runtime.DefaultYAMLEncoding.Unmarshal([]byte(args[2]), &o.Base); err != nil {
return errors.Wrapf(err, "invalid base input specification")
}
return nil
}

func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error {
m := p.GetInputType(opts.Name)
if m == nil {
return errors.ErrUnknown(descriptor.KIND_INPUTTYPE, opts.Name)
}
err := opts.Options.ConvertFor(m.Options()...)
if err != nil {
return err
}
err = m.ComposeSpecification(p, opts.Options, opts.Base)
if err != nil {
return err
}
data, err := json.Marshal(opts.Base)
if err != nil {
return err
}
cmd.Printf("%s\n", string(data))
return nil
}
87 changes: 87 additions & 0 deletions api/ocm/plugin/ppi/cmds/input/get/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package get

import (
"encoding/json"
"fmt"
"io"
"os"

"github.com/mandelsoft/goutils/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"

"ocm.software/ocm/api/credentials"
"ocm.software/ocm/api/ocm/plugin/descriptor"
"ocm.software/ocm/api/ocm/plugin/ppi"
commonppi "ocm.software/ocm/api/ocm/plugin/ppi/cmds/common"
"ocm.software/ocm/api/utils/cobrautils/flag"
"ocm.software/ocm/api/utils/runtime"
)

const (
Name = "get"
OptCreds = commonppi.OptCreds
)

func New(p ppi.Plugin) *cobra.Command {
opts := Options{}

cmd := &cobra.Command{
Use: Name + " [<flags>] <access spec>",
Short: "get blob",
Long: `
Evaluate the given input specification and return the described blob on
*stdout*.`,
Args: cobra.ExactArgs(1),
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
return opts.Complete(args)
},
RunE: func(cmd *cobra.Command, args []string) error {
return Command(p, cmd, &opts)
},
}
opts.AddFlags(cmd.Flags())
return cmd
}

type Options struct {
Credentials credentials.DirectCredentials
Specification json.RawMessage
}

func (o *Options) AddFlags(fs *pflag.FlagSet) {
flag.YAMLVarP(fs, &o.Credentials, OptCreds, "c", nil, "credentials")
flag.StringToStringVarPFA(fs, &o.Credentials, "credential", "C", nil, "dedicated credential value")
}

func (o *Options) Complete(args []string) error {
if err := runtime.DefaultYAMLEncoding.Unmarshal([]byte(args[0]), &o.Specification); err != nil {
return errors.Wrapf(err, "invalid repository specification")
}

fmt.Fprintf(os.Stderr, "credentials: %s\n", o.Credentials.String())
return nil
}

func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error {
spec, err := p.DecodeInputSpecification(opts.Specification)
if err != nil {
return errors.Wrapf(err, "access specification")
}

m := p.GetInputType(spec.GetType())
if m == nil {
return errors.ErrUnknown(descriptor.KIND_INPUTTYPE, 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
}
Loading

0 comments on commit 5dd5512

Please sign in to comment.