diff --git a/.github/config/wordlist.txt b/.github/config/wordlist.txt
index d38b0d4469..5fe1dc541a 100644
--- a/.github/config/wordlist.txt
+++ b/.github/config/wordlist.txt
@@ -281,6 +281,7 @@ toi
toiexecutor
toolset
transportarchive
+transferhandler
typehandler
typename
unmarshaller
diff --git a/api/credentials/cpi/repotypes.go b/api/credentials/cpi/repotypes.go
index 673998c36e..e76e10fcfa 100644
--- a/api/credentials/cpi/repotypes.go
+++ b/api/credentials/cpi/repotypes.go
@@ -25,15 +25,15 @@ func RegisterRepositoryTypeVersions(s RepositoryTypeVersionScheme) {
////////////////////////////////////////////////////////////////////////////////
func NewRepositoryType[I RepositorySpec](name string, opts ...RepositoryOption) RepositoryType {
- return descriptivetype.NewTypedObjectTypeObject(runtime.NewVersionedTypedObjectType[RepositorySpec, I](name), opts...)
+ return descriptivetype.NewVersionedTypedObjectTypeObject(runtime.NewVersionedTypedObjectType[RepositorySpec, I](name), opts...)
}
func NewRepositoryTypeByConverter[I RepositorySpec, V runtime.TypedObject](name string, converter runtime.Converter[I, V], opts ...RepositoryOption) RepositoryType {
- return descriptivetype.NewTypedObjectTypeObject(runtime.NewVersionedTypedObjectTypeByConverter[RepositorySpec, I](name, converter), opts...)
+ return descriptivetype.NewVersionedTypedObjectTypeObject(runtime.NewVersionedTypedObjectTypeByConverter[RepositorySpec, I](name, converter), opts...)
}
func NewRepositoryTypeByFormatVersion(name string, fmt runtime.FormatVersion[RepositorySpec], opts ...RepositoryOption) RepositoryType {
- return descriptivetype.NewTypedObjectTypeObject(runtime.NewVersionedTypedObjectTypeByFormatVersion[RepositorySpec](name, fmt), opts...)
+ return descriptivetype.NewVersionedTypedObjectTypeObject(runtime.NewVersionedTypedObjectTypeByFormatVersion[RepositorySpec](name, fmt), opts...)
}
////////////////////////////////////////////////////////////////////////////////
diff --git a/api/credentials/internal/repository.go b/api/credentials/internal/repository.go
index 337d2f4bc8..3f0a306d2b 100644
--- a/api/credentials/internal/repository.go
+++ b/api/credentials/internal/repository.go
@@ -3,7 +3,7 @@ package internal
import (
"github.com/mandelsoft/goutils/set"
- common "ocm.software/ocm/api/utils/misc"
+ "ocm.software/ocm/api/utils/misc"
)
type Repository interface {
@@ -17,16 +17,16 @@ type Credentials interface {
ExistsProperty(name string) bool
GetProperty(name string) string
PropertyNames() set.Set[string]
- Properties() common.Properties
+ Properties() misc.Properties
}
-type DirectCredentials common.Properties
+type DirectCredentials misc.Properties
var _ Credentials = (*DirectCredentials)(nil)
-func NewCredentials(props common.Properties) DirectCredentials {
+func NewCredentials(props misc.Properties) DirectCredentials {
if props == nil {
- props = common.Properties{}
+ props = misc.Properties{}
} else {
props = props.Copy()
}
@@ -43,11 +43,11 @@ func (c DirectCredentials) GetProperty(name string) string {
}
func (c DirectCredentials) PropertyNames() set.Set[string] {
- return common.Properties(c).Names()
+ return misc.Properties(c).Names()
}
-func (c DirectCredentials) Properties() common.Properties {
- return common.Properties(c).Copy()
+func (c DirectCredentials) Properties() misc.Properties {
+ return misc.Properties(c).Copy()
}
func (c DirectCredentials) Credentials(Context, ...CredentialsSource) (Credentials, error) {
@@ -55,9 +55,9 @@ func (c DirectCredentials) Credentials(Context, ...CredentialsSource) (Credentia
}
func (c DirectCredentials) Copy() DirectCredentials {
- return DirectCredentials(common.Properties(c).Copy())
+ return DirectCredentials(misc.Properties(c).Copy())
}
func (c DirectCredentials) String() string {
- return common.Properties(c).String()
+ return misc.Properties(c).String()
}
diff --git a/api/credentials/internal/repotypes.go b/api/credentials/internal/repotypes.go
index dafe999f32..3d2ddad17a 100644
--- a/api/credentials/internal/repotypes.go
+++ b/api/credentials/internal/repotypes.go
@@ -13,7 +13,7 @@ import (
)
type RepositoryType interface {
- descriptivetype.TypedObjectType[RepositorySpec]
+ descriptivetype.VersionedTypedObjectType[RepositorySpec]
}
type RepositorySpec interface {
@@ -28,22 +28,22 @@ type (
)
type RepositoryTypeScheme interface {
- descriptivetype.TypeScheme[RepositorySpec, RepositoryType]
+ descriptivetype.VersionedTypeScheme[RepositorySpec, RepositoryType]
}
-type _Scheme = descriptivetype.TypeScheme[RepositorySpec, RepositoryType]
+type _Scheme = descriptivetype.VersionedTypeScheme[RepositorySpec, RepositoryType]
type repositoryTypeScheme struct {
_Scheme
}
func NewRepositoryTypeScheme(defaultDecoder RepositorySpecDecoder, base ...RepositoryTypeScheme) RepositoryTypeScheme {
- scheme := descriptivetype.MustNewDefaultTypeScheme[RepositorySpec, RepositoryType, RepositoryTypeScheme]("Credential provider", nil, &UnknownRepositorySpec{}, true, defaultDecoder, utils.Optional(base...))
+ scheme := descriptivetype.MustNewDefaultVersionedTypeScheme[RepositorySpec, RepositoryType, RepositoryTypeScheme]("Credential provider", nil, &UnknownRepositorySpec{}, true, defaultDecoder, utils.Optional(base...))
return &repositoryTypeScheme{scheme}
}
func NewStrictRepositoryTypeScheme(base ...RepositoryTypeScheme) runtime.VersionedTypeRegistry[RepositorySpec, RepositoryType] {
- scheme := descriptivetype.MustNewDefaultTypeScheme[RepositorySpec, RepositoryType, RepositoryTypeScheme]("Credential provider", nil, nil, false, nil, utils.Optional(base...))
+ scheme := descriptivetype.MustNewDefaultVersionedTypeScheme[RepositorySpec, RepositoryType, RepositoryTypeScheme]("Credential provider", nil, nil, false, nil, utils.Optional(base...))
return &repositoryTypeScheme{scheme}
}
diff --git a/api/oci/utils.go b/api/oci/utils.go
index 33e34d4f2a..0d3e3e07be 100644
--- a/api/oci/utils.go
+++ b/api/oci/utils.go
@@ -19,10 +19,14 @@ func AsTags(tag string) []string {
}
func StandardOCIRef(host, repository, version string) string {
+ return fmt.Sprintf("%s%s%s", host, grammar.RepositorySeparator, RelativeOCIRef(repository, version))
+}
+
+func RelativeOCIRef(repository, version string) string {
sep := grammar.TagSeparator
i := strings.Index(version, grammar.DigestSeparator)
if i > 1 {
- return fmt.Sprintf("%s%s%s%s%s", host, grammar.RepositorySeparator, repository, sep, version)
+ return fmt.Sprintf("%s%s%s", repository, sep, version)
}
if ok, _ := artdesc.IsDigest(version); ok {
sep = grammar.DigestSeparator
@@ -30,7 +34,7 @@ func StandardOCIRef(host, repository, version string) string {
sep = ""
}
}
- return fmt.Sprintf("%s%s%s%s%s", host, grammar.RepositorySeparator, repository, sep, version)
+ return fmt.Sprintf("%s%s%s", repository, sep, version)
}
func IsIntermediate(spec RepositorySpec) bool {
diff --git a/api/ocm/cpi/accspeccpi/accesstypes.go b/api/ocm/cpi/accspeccpi/accesstypes.go
index e71fcc9b84..e9a954d2f0 100644
--- a/api/ocm/cpi/accspeccpi/accesstypes.go
+++ b/api/ocm/cpi/accspeccpi/accesstypes.go
@@ -32,13 +32,13 @@ func MustNewAccessSpecMultiFormatVersion(kind string, formats AccessSpecFormatVe
}
func NewAccessSpecType[I AccessSpec](name string, opts ...AccessSpecTypeOption) AccessType {
- return flagsetscheme.NewTypedObjectTypeObject[AccessSpec](runtime.NewVersionedTypedObjectType[AccessSpec, I](name), opts...)
+ return flagsetscheme.NewVersionedTypedObjectTypeObject[AccessSpec](runtime.NewVersionedTypedObjectType[AccessSpec, I](name), opts...)
}
func NewAccessSpecTypeByConverter[I AccessSpec, V runtime.VersionedTypedObject](name string, converter runtime.Converter[I, V], opts ...AccessSpecTypeOption) AccessType {
- return flagsetscheme.NewTypedObjectTypeObject[AccessSpec](runtime.NewVersionedTypedObjectTypeByConverter[AccessSpec, I, V](name, converter), opts...)
+ return flagsetscheme.NewVersionedTypedObjectTypeObject[AccessSpec](runtime.NewVersionedTypedObjectTypeByConverter[AccessSpec, I, V](name, converter), opts...)
}
func NewAccessSpecTypeByFormatVersion(name string, fmt runtime.FormatVersion[AccessSpec], opts ...AccessSpecTypeOption) AccessType {
- return flagsetscheme.NewTypedObjectTypeObject[AccessSpec](runtime.NewVersionedTypedObjectTypeByFormatVersion[AccessSpec](name, fmt), opts...)
+ return flagsetscheme.NewVersionedTypedObjectTypeObject[AccessSpec](runtime.NewVersionedTypedObjectTypeByFormatVersion[AccessSpec](name, fmt), opts...)
}
diff --git a/api/ocm/cpi/accspeccpi/interface.go b/api/ocm/cpi/accspeccpi/interface.go
index 044fe9b2cd..6809c4a806 100644
--- a/api/ocm/cpi/accspeccpi/interface.go
+++ b/api/ocm/cpi/accspeccpi/interface.go
@@ -11,10 +11,11 @@ type (
AccessType = internal.AccessType
- AccessMethodImpl = internal.AccessMethodImpl
- AccessMethod = internal.AccessMethod
- AccessSpec = internal.AccessSpec
- AccessSpecRef = internal.AccessSpecRef
+ AccessMethodImpl = internal.AccessMethodImpl
+ AccessMethod = internal.AccessMethod
+ UniformAccessSpecInfo = internal.UniformAccessSpecInfo
+ AccessSpec = internal.AccessSpec
+ AccessSpecRef = internal.AccessSpecRef
HintProvider = internal.HintProvider
GlobalAccessProvider = internal.GlobalAccessProvider
diff --git a/api/ocm/cpi/interface.go b/api/ocm/cpi/interface.go
index cfda6966a1..923470acdb 100644
--- a/api/ocm/cpi/interface.go
+++ b/api/ocm/cpi/interface.go
@@ -50,6 +50,7 @@ type (
ComponentLister = internal.ComponentLister
ComponentAccess = internal.ComponentAccess
ComponentVersionAccess = internal.ComponentVersionAccess
+ UniformAccessSpecInfo = internal.UniformAccessSpecInfo
AccessSpec = internal.AccessSpec
AccessSpecDecoder = internal.AccessSpecDecoder
GenericAccessSpec = internal.GenericAccessSpec
@@ -183,6 +184,8 @@ func ToGenericRepositorySpec(spec RepositorySpec) (*GenericRepositorySpec, error
type AccessSpecRef = internal.AccessSpecRef
+var _ AccessSpec = &AccessSpecRef{}
+
func NewAccessSpecRef(spec AccessSpec) *AccessSpecRef {
return internal.NewAccessSpecRef(spec)
}
diff --git a/api/ocm/extensions/accessmethods/compose/method.go b/api/ocm/extensions/accessmethods/compose/method.go
index 9228d2b1c0..937fe48514 100644
--- a/api/ocm/extensions/accessmethods/compose/method.go
+++ b/api/ocm/extensions/accessmethods/compose/method.go
@@ -68,6 +68,12 @@ func (a *AccessSpec) Describe(ctx accspeccpi.Context) string {
return fmt.Sprintf("Composition blob %s", a.Id)
}
+func (a *AccessSpec) Info(ctx accspeccpi.Context) *accspeccpi.UniformAccessSpecInfo {
+ return &accspeccpi.UniformAccessSpecInfo{
+ Kind: Type,
+ }
+}
+
func (_ *AccessSpec) IsLocal(accspeccpi.Context) bool {
return true
}
diff --git a/api/ocm/extensions/accessmethods/github/method.go b/api/ocm/extensions/accessmethods/github/method.go
index c92ed2c1fe..1086ef7cbb 100644
--- a/api/ocm/extensions/accessmethods/github/method.go
+++ b/api/ocm/extensions/accessmethods/github/method.go
@@ -105,6 +105,20 @@ func (a *AccessSpec) Describe(ctx accspeccpi.Context) string {
return fmt.Sprintf("GitHub commit %s[%s]", a.RepoURL, a.Commit)
}
+func (a *AccessSpec) Info(ctx accspeccpi.Context) *accspeccpi.UniformAccessSpecInfo {
+ u, err := url.Parse(a.RepoURL)
+ if err != nil {
+ u = &url.URL{}
+ }
+ return &accspeccpi.UniformAccessSpecInfo{
+ Kind: Type,
+ Host: u.Hostname(),
+ Port: u.Port(),
+ Path: u.Path,
+ Info: a.Commit,
+ }
+}
+
func (_ *AccessSpec) IsLocal(accspeccpi.Context) bool {
return false
}
diff --git a/api/ocm/extensions/accessmethods/helm/method.go b/api/ocm/extensions/accessmethods/helm/method.go
index 683952a6b4..37d5697b36 100644
--- a/api/ocm/extensions/accessmethods/helm/method.go
+++ b/api/ocm/extensions/accessmethods/helm/method.go
@@ -3,6 +3,7 @@ package helm
import (
"fmt"
"io"
+ "net/url"
"strings"
"sync"
@@ -63,6 +64,20 @@ func (a *AccessSpec) Describe(ctx accspeccpi.Context) string {
return fmt.Sprintf("Helm chart %s:%s in repository %s", a.GetChartName(), a.GetVersion(), a.HelmRepository)
}
+func (a *AccessSpec) Info(ctx accspeccpi.Context) *accspeccpi.UniformAccessSpecInfo {
+ u, err := url.Parse(a.HelmRepository)
+ if err != nil {
+ u = &url.URL{}
+ }
+ return &accspeccpi.UniformAccessSpecInfo{
+ Kind: Type,
+ Host: u.Hostname(),
+ Port: u.Port(),
+ Path: u.Path,
+ Info: a.HelmChart,
+ }
+}
+
func (a *AccessSpec) IsLocal(ctx accspeccpi.Context) bool {
return false
}
diff --git a/api/ocm/extensions/accessmethods/localblob/method.go b/api/ocm/extensions/accessmethods/localblob/method.go
index 83fbcc4b05..2799f29c9a 100644
--- a/api/ocm/extensions/accessmethods/localblob/method.go
+++ b/api/ocm/extensions/accessmethods/localblob/method.go
@@ -115,6 +115,13 @@ func (a *AccessSpec) Describe(ctx accspeccpi.Context) string {
return fmt.Sprintf("Local blob %s[%s]", a.LocalReference, a.ReferenceName)
}
+func (a *AccessSpec) Info(ctx accspeccpi.Context) *accspeccpi.UniformAccessSpecInfo {
+ return &accspeccpi.UniformAccessSpecInfo{
+ Kind: Type,
+ Info: a.LocalReference,
+ }
+}
+
func (a *AccessSpec) IsLocal(accspeccpi.Context) bool {
return true
}
diff --git a/api/ocm/extensions/accessmethods/maven/method.go b/api/ocm/extensions/accessmethods/maven/method.go
index e37cad5bc1..a9433c7c89 100644
--- a/api/ocm/extensions/accessmethods/maven/method.go
+++ b/api/ocm/extensions/accessmethods/maven/method.go
@@ -2,6 +2,7 @@ package maven
import (
"fmt"
+ "net/url"
"github.com/mandelsoft/goutils/optionutils"
@@ -78,6 +79,20 @@ func (a *AccessSpec) Describe(_ accspeccpi.Context) string {
return fmt.Sprintf("Maven package '%s' in repository '%s' path '%s'", a.Coordinates.String(), a.RepoUrl, a.Coordinates.FilePath())
}
+func (a *AccessSpec) Info(ctx accspeccpi.Context) *accspeccpi.UniformAccessSpecInfo {
+ u, err := url.Parse(a.RepoUrl)
+ if err != nil {
+ u = &url.URL{}
+ }
+ return &accspeccpi.UniformAccessSpecInfo{
+ Kind: Type,
+ Host: u.Hostname(),
+ Port: u.Port(),
+ Path: u.Path,
+ Info: a.Coordinates.String(),
+ }
+}
+
func (_ *AccessSpec) IsLocal(accspeccpi.Context) bool {
return false
}
diff --git a/api/ocm/extensions/accessmethods/none/method.go b/api/ocm/extensions/accessmethods/none/method.go
index 7804cd8783..dbfbced391 100644
--- a/api/ocm/extensions/accessmethods/none/method.go
+++ b/api/ocm/extensions/accessmethods/none/method.go
@@ -43,6 +43,12 @@ func (a *AccessSpec) Describe(ctx accspeccpi.Context) string {
return "none"
}
+func (a *AccessSpec) Info(ctx accspeccpi.Context) *accspeccpi.UniformAccessSpecInfo {
+ return &accspeccpi.UniformAccessSpecInfo{
+ Kind: Type,
+ }
+}
+
func (s *AccessSpec) IsLocal(context accspeccpi.Context) bool {
return false
}
diff --git a/api/ocm/extensions/accessmethods/npm/method.go b/api/ocm/extensions/accessmethods/npm/method.go
index ed944f24fe..c7f0e73c0c 100644
--- a/api/ocm/extensions/accessmethods/npm/method.go
+++ b/api/ocm/extensions/accessmethods/npm/method.go
@@ -2,6 +2,7 @@ package npm
import (
"fmt"
+ "net/url"
"ocm.software/ocm/api/ocm/cpi/accspeccpi"
"ocm.software/ocm/api/utils/blobaccess/blobaccess"
@@ -49,6 +50,20 @@ func (a *AccessSpec) Describe(_ accspeccpi.Context) string {
return fmt.Sprintf("NPM package %s:%s in registry %s", a.Package, a.Version, a.Registry)
}
+func (a *AccessSpec) Info(ctx accspeccpi.Context) *accspeccpi.UniformAccessSpecInfo {
+ u, err := url.Parse(a.Registry)
+ if err != nil {
+ u = &url.URL{}
+ }
+ return &accspeccpi.UniformAccessSpecInfo{
+ Kind: Type,
+ Host: u.Hostname(),
+ Port: u.Port(),
+ Path: u.Path,
+ Info: a.GetReferenceHint(nil),
+ }
+}
+
func (_ *AccessSpec) IsLocal(accspeccpi.Context) bool {
return false
}
diff --git a/api/ocm/extensions/accessmethods/ociartifact/method.go b/api/ocm/extensions/accessmethods/ociartifact/method.go
index 38fef2b5ef..d243c02db9 100644
--- a/api/ocm/extensions/accessmethods/ociartifact/method.go
+++ b/api/ocm/extensions/accessmethods/ociartifact/method.go
@@ -75,6 +75,25 @@ func (a *AccessSpec) Describe(ctx accspeccpi.Context) string {
return fmt.Sprintf("OCI artifact %s", a.ImageReference)
}
+func (a *AccessSpec) Info(ctx accspeccpi.Context) *accspeccpi.UniformAccessSpecInfo {
+ ref, _ := oci.ParseRef(a.ImageReference)
+ host, port := ref.HostPort()
+
+ r := ref.Repository
+ if ref.Tag != nil {
+ r += ":" + *ref.Tag
+ }
+ if ref.Digest != nil {
+ r += "@" + ref.Digest.String()
+ }
+ return &accspeccpi.UniformAccessSpecInfo{
+ Kind: Type,
+ Host: host,
+ Port: port,
+ Info: r,
+ }
+}
+
func (_ *AccessSpec) IsLocal(accspeccpi.Context) bool {
return false
}
diff --git a/api/ocm/extensions/accessmethods/ociblob/method.go b/api/ocm/extensions/accessmethods/ociblob/method.go
index aad99685e0..a38f6033d9 100644
--- a/api/ocm/extensions/accessmethods/ociblob/method.go
+++ b/api/ocm/extensions/accessmethods/ociblob/method.go
@@ -3,6 +3,7 @@ package ociblob
import (
"fmt"
"io"
+ "strings"
"sync"
"github.com/mandelsoft/goutils/errors"
@@ -62,6 +63,22 @@ func (a *AccessSpec) Describe(ctx accspeccpi.Context) string {
return fmt.Sprintf("OCI blob %s in repository %s", a.Digest, a.Reference)
}
+func (a *AccessSpec) Info(ctx accspeccpi.Context) *accspeccpi.UniformAccessSpecInfo {
+ segs := strings.Split(a.Reference, "/")
+ comps := strings.Split(segs[0], ":")
+ port := ""
+ if len(comps) > 1 {
+ port = comps[1]
+ }
+ return &accspeccpi.UniformAccessSpecInfo{
+ Kind: Type,
+ Host: comps[0],
+ Port: port,
+ Path: strings.Join(segs[1:], "/"),
+ Info: a.Digest.String(),
+ }
+}
+
func (s *AccessSpec) IsLocal(context accspeccpi.Context) bool {
return false
}
diff --git a/api/ocm/extensions/accessmethods/ocm/method.go b/api/ocm/extensions/accessmethods/ocm/method.go
index f48edac666..0e3c16e445 100644
--- a/api/ocm/extensions/accessmethods/ocm/method.go
+++ b/api/ocm/extensions/accessmethods/ocm/method.go
@@ -77,6 +77,21 @@ func (a *AccessSpec) Describe(ctx accspeccpi.Context) string {
return fmt.Sprintf("OCM resource %s%s", a.ResourceRef.String(), comp)
}
+func (a *AccessSpec) Info(ctx accspeccpi.Context) *accspeccpi.UniformAccessSpecInfo {
+ ref := a.OCMRepository.AsUniformSpec(ctx)
+ if ref == nil {
+ return &accspeccpi.UniformAccessSpecInfo{Kind: Type}
+ }
+ host, port := ref.HostPort()
+ return &accspeccpi.UniformAccessSpecInfo{
+ Kind: Type,
+ Host: host,
+ Port: port,
+ Path: ref.SubPath,
+ Info: ref.Info,
+ }
+}
+
func (a *AccessSpec) IsLocal(ctx accspeccpi.Context) bool {
return false
}
diff --git a/api/ocm/extensions/accessmethods/options/deprecated.go b/api/ocm/extensions/accessmethods/options/deprecated.go
new file mode 100644
index 0000000000..c175f6b3a7
--- /dev/null
+++ b/api/ocm/extensions/accessmethods/options/deprecated.go
@@ -0,0 +1,97 @@
+package options
+
+import (
+ "ocm.software/ocm/api/utils/cobrautils/flagsets"
+)
+
+const (
+ // Deprecated: use package [flagsets].
+ TYPE_STRING = flagsets.TYPE_STRING
+ // Deprecated: use package [flagsets].
+ TYPE_STRINGARRAY = flagsets.TYPE_STRINGARRAY
+ // Deprecated: use package [flagsets].
+ TYPE_STRING2STRING = flagsets.TYPE_STRING2STRING
+ // Deprecated: use package [flagsets].
+ TYPE_INT = flagsets.TYPE_INT
+ // Deprecated: use package [flagsets].
+ TYPE_BOOL = flagsets.TYPE_BOOL
+ // Deprecated: use package [flagsets].
+ TYPE_YAML = flagsets.TYPE_YAML
+ // Deprecated: use package [flagsets].
+ TYPE_STRINGMAPYAML = flagsets.TYPE_STRINGMAPYAML
+ // Deprecated: use package [flagsets].
+ TYPE_STRING2YAML = flagsets.TYPE_STRING2YAML
+ // Deprecated: use package [flagsets].
+ TYPE_STRING2STRINGSLICE = flagsets.TYPE_STRING2STRINGSLICE
+ // Deprecated: use package [flagsets].
+ TYPE_STRINGCOLONSTRINGSLICE = flagsets.TYPE_STRINGCOLONSTRINGSLICE
+ // Deprecated: use package [flagsets].
+ TYPE_BYTES = flagsets.TYPE_BYTES
+ // Deprecated: use package [flagsets].
+ TYPE_IDENTITYPATH = flagsets.TYPE_IDENTITYPATH
+)
+
+// Deprecated: use packagge [flagsets].
+type OptionType = flagsets.ConfigOptionType
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Deprecated: use packagge [flagsets].
+func NewStringOptionType(name, desc string) OptionType {
+ return flagsets.NewStringOptionType(name, desc)
+}
+
+// Deprecated: use packagge [flagsets].
+func NewStringArrayOptionType(name, desc string) OptionType {
+ return flagsets.NewStringArrayOptionType(name, desc)
+}
+
+// Deprecated: use packagge [flagsets].
+func NewIntOptionType(name, desc string) OptionType {
+ return flagsets.NewIntOptionType(name, desc)
+}
+
+// Deprecated: use packagge [flagsets].
+func NewBoolOptionType(name, desc string) OptionType {
+ return flagsets.NewBoolOptionType(name, desc)
+}
+
+// Deprecated: use packagge [flagsets].
+func NewYAMLOptionType(name, desc string) OptionType {
+ return flagsets.NewYAMLOptionType(name, desc)
+}
+
+// Deprecated: use packagge [flagsets].
+func NewValueMapYAMLOptionType(name, desc string) OptionType {
+ return flagsets.NewValueMapYAMLOptionType(name, desc)
+}
+
+// Deprecated: use packagge [flagsets].
+func NewValueMapOptionType(name, desc string) OptionType {
+ return flagsets.NewValueMapOptionType(name, desc)
+}
+
+// Deprecated: use packagge [flagsets].
+func NewStringMapOptionType(name, desc string) OptionType {
+ return flagsets.NewStringMapOptionType(name, desc)
+}
+
+// Deprecated: use packagge [flagsets].
+func NewStringSliceMapOptionType(name, desc string) OptionType {
+ return flagsets.NewStringSliceMapOptionType(name, desc)
+}
+
+// Deprecated: use packagge [flagsets].
+func NewStringSliceMapColonOptionType(name, desc string) OptionType {
+ return flagsets.NewStringSliceMapColonOptionType(name, desc)
+}
+
+// Deprecated: use packagge [flagsets].
+func NewBytesOptionType(name, desc string) OptionType {
+ return flagsets.NewBytesOptionType(name, desc)
+}
+
+// Deprecated: use packagge [flagsets].
+func NewIdentityPathOptionType(name, desc string) OptionType {
+ return flagsets.NewIdentityPathOptionType(name, desc)
+}
diff --git a/api/ocm/extensions/accessmethods/options/init.go b/api/ocm/extensions/accessmethods/options/init.go
index 7e815b8f30..f0ff625e01 100644
--- a/api/ocm/extensions/accessmethods/options/init.go
+++ b/api/ocm/extensions/accessmethods/options/init.go
@@ -1,34 +1,10 @@
package options
-const (
- TYPE_STRING = "string"
- TYPE_STRINGARRAY = "[]string"
- TYPE_STRING2STRING = "string=string"
- TYPE_INT = "int"
- TYPE_BOOL = "bool"
- TYPE_YAML = "YAML"
- TYPE_STRINGMAPYAML = "map[string]YAML"
- TYPE_STRING2YAML = "string=YAML"
- TYPE_STRING2STRINGSLICE = "string=string,string"
- TYPE_STRINGCOLONSTRINGSLICE = "string:string,string"
- TYPE_BYTES = "[]byte"
- TYPE_IDENTITYPATH = "[]identity"
+import (
+ "ocm.software/ocm/api/utils/cobrautils/flagsets"
)
-func init() {
- DefaultRegistry.RegisterValueType(TYPE_STRING, NewStringOptionType, "string value")
- DefaultRegistry.RegisterValueType(TYPE_STRINGARRAY, NewStringArrayOptionType, "list of string values")
- DefaultRegistry.RegisterValueType(TYPE_STRING2STRING, NewStringMapOptionType, "string map defined by dedicated assignments")
- DefaultRegistry.RegisterValueType(TYPE_INT, NewIntOptionType, "integer value")
- DefaultRegistry.RegisterValueType(TYPE_BOOL, NewBoolOptionType, "boolean flag")
- DefaultRegistry.RegisterValueType(TYPE_YAML, NewYAMLOptionType, "JSON or YAML document string")
- DefaultRegistry.RegisterValueType(TYPE_STRINGMAPYAML, NewValueMapYAMLOptionType, "JSON or YAML map")
- DefaultRegistry.RegisterValueType(TYPE_STRING2YAML, NewValueMapOptionType, "string map with arbitrary values defined by dedicated assignments")
- DefaultRegistry.RegisterValueType(TYPE_STRING2STRINGSLICE, NewStringSliceMapOptionType, "string map defined by dedicated assignment of comma separated strings")
- DefaultRegistry.RegisterValueType(TYPE_STRINGCOLONSTRINGSLICE, NewStringSliceMapColonOptionType, "string map defined by dedicated assignment of comma separated strings")
- DefaultRegistry.RegisterValueType(TYPE_BYTES, NewBytesOptionType, "byte value")
- DefaultRegistry.RegisterValueType(TYPE_IDENTITYPATH, NewIdentityPathOptionType, "identity path")
-}
+var DefaultRegistry = flagsets.SetBaseTypes(flagsets.NewConfigOptionTypeRegistry())
func RegisterOption(o OptionType) OptionType {
DefaultRegistry.RegisterOptionType(o)
diff --git a/api/ocm/extensions/accessmethods/options/registry_test.go b/api/ocm/extensions/accessmethods/options/registry_test.go
deleted file mode 100644
index 675823db0f..0000000000
--- a/api/ocm/extensions/accessmethods/options/registry_test.go
+++ /dev/null
@@ -1,50 +0,0 @@
-package options
-
-import (
- . "github.com/mandelsoft/goutils/testutils"
- . "github.com/onsi/ginkgo/v2"
- . "github.com/onsi/gomega"
-)
-
-var _ = Describe("registry", func() {
- var reg Registry
-
- BeforeEach(func() {
- reg = New()
- })
-
- It("sets and retrieves type", func() {
- reg.RegisterValueType("string", NewStringOptionType, "string")
-
- t := reg.GetValueType("string")
- Expect(t).NotTo(BeNil())
-
- o := Must(reg.CreateOptionType("string", "test", "some test"))
- Expect(o.GetName()).To(Equal("test"))
- Expect(o.GetDescription()).To(Equal("[*string*] some test"))
- })
-
- It("sets and retrieves option", func() {
- reg.RegisterOptionType(HostnameOption)
-
- t := reg.GetOptionType(HostnameOption.GetName())
- Expect(t).NotTo(BeNil())
- })
-
- It("creates merges a new type", func() {
- reg.RegisterValueType("string", NewStringOptionType, "string")
- reg.RegisterOptionType(HostnameOption)
-
- o := Must(reg.CreateOptionType("string", HostnameOption.GetName(), "some test"))
- Expect(o).To(BeIdenticalTo(HostnameOption))
- })
-
- It("fails creating existing", func() {
- reg.RegisterValueType("string", NewStringOptionType, "string")
- reg.RegisterValueType("int", NewIntOptionType, "int")
- reg.RegisterOptionType(HostnameOption)
-
- _, err := reg.CreateOptionType("int", HostnameOption.GetName(), "some test")
- MustFailWithMessage(err, "option \"accessHostname\" already exists")
- })
-})
diff --git a/api/ocm/extensions/accessmethods/options/standard.go b/api/ocm/extensions/accessmethods/options/standard.go
index a2b6ccb99b..0ebefbbd7d 100644
--- a/api/ocm/extensions/accessmethods/options/standard.go
+++ b/api/ocm/extensions/accessmethods/options/standard.go
@@ -1,84 +1,88 @@
package options
+import (
+ "ocm.software/ocm/api/utils/cobrautils/flagsets"
+)
+
// HintOption .
-var HintOption = RegisterOption(NewStringOptionType("hint", "(repository) hint for local artifacts"))
+var HintOption = RegisterOption(flagsets.NewStringOptionType("hint", "(repository) hint for local artifacts"))
// MediatypeOption .
-var MediatypeOption = RegisterOption(NewStringOptionType("mediaType", "media type for artifact blob representation"))
+var MediatypeOption = RegisterOption(flagsets.NewStringOptionType("mediaType", "media type for artifact blob representation"))
// SizeOption .
-var SizeOption = RegisterOption(NewIntOptionType("size", "blob size"))
+var SizeOption = RegisterOption(flagsets.NewIntOptionType("size", "blob size"))
// DigestOption .
-var DigestOption = RegisterOption(NewStringOptionType("digest", "blob digest"))
+var DigestOption = RegisterOption(flagsets.NewStringOptionType("digest", "blob digest"))
// ReferenceOption .
-var ReferenceOption = RegisterOption(NewStringOptionType("reference", "reference name"))
+var ReferenceOption = RegisterOption(flagsets.NewStringOptionType("reference", "reference name"))
// PackageOption .
-var PackageOption = RegisterOption(NewStringOptionType("package", "package or object name"))
+var PackageOption = RegisterOption(flagsets.NewStringOptionType("package", "package or object name"))
// ArtifactOption .
-var ArtifactOption = RegisterOption(NewStringOptionType("artifactId", "maven artifact id"))
+var ArtifactOption = RegisterOption(flagsets.NewStringOptionType("artifactId", "maven artifact id"))
// GroupOption .
-var GroupOption = RegisterOption(NewStringOptionType("groupId", "maven group id"))
+var GroupOption = RegisterOption(flagsets.NewStringOptionType("groupId", "maven group id"))
// RepositoryOption .
-var RepositoryOption = RegisterOption(NewStringOptionType("accessRepository", "repository or registry URL"))
+var RepositoryOption = RegisterOption(flagsets.NewStringOptionType("accessRepository", "repository or registry URL"))
// HostnameOption .
-var HostnameOption = RegisterOption(NewStringOptionType("accessHostname", "hostname used for access"))
+var HostnameOption = RegisterOption(flagsets.NewStringOptionType("accessHostname", "hostname used for access"))
// CommitOption .
-var CommitOption = RegisterOption(NewStringOptionType("commit", "git commit id"))
+var CommitOption = RegisterOption(flagsets.NewStringOptionType("commit", "git commit id"))
// GlobalAccessOption .
-var GlobalAccessOption = RegisterOption(NewValueMapYAMLOptionType("globalAccess", "access specification for global access"))
+var GlobalAccessOption = RegisterOption(flagsets.NewValueMapYAMLOptionType("globalAccess", "access specification for global access"))
// RegionOption .
-var RegionOption = RegisterOption(NewStringOptionType("region", "region name"))
+var RegionOption = RegisterOption(flagsets.NewStringOptionType("region", "region name"))
// BucketOption .
-var BucketOption = RegisterOption(NewStringOptionType("bucket", "bucket name"))
+var BucketOption = RegisterOption(flagsets.NewStringOptionType("bucket", "bucket name"))
// VersionOption .
-var VersionOption = RegisterOption(NewStringOptionType("accessVersion", "version for access specification"))
+var VersionOption = RegisterOption(flagsets.NewStringOptionType("accessVersion", "version for access specification"))
// ComponentOption.
-var ComponentOption = RegisterOption(NewStringOptionType("accessComponent", "component for access specification"))
+var ComponentOption = RegisterOption(flagsets.NewStringOptionType("accessComponent", "component for access specification"))
// IdentityPathOption.
-var IdentityPathOption = RegisterOption(NewIdentityPathOptionType("identityPath", "identity path for specification"))
+var IdentityPathOption = RegisterOption(flagsets.NewIdentityPathOptionType("identityPath", "identity path for specification"))
// URLOption.
-var URLOption = RegisterOption(NewStringOptionType("url", "artifact or server url"))
+var URLOption = RegisterOption(flagsets.NewStringOptionType("url", "artifact or server url"))
-var HTTPHeaderOption = RegisterOption(NewStringSliceMapColonOptionType("header", "http headers"))
+var HTTPHeaderOption = RegisterOption(flagsets.NewStringSliceMapColonOptionType("header", "http headers"))
-var HTTPVerbOption = RegisterOption(NewStringOptionType("verb", "http request method"))
+var HTTPVerbOption = RegisterOption(flagsets.NewStringOptionType("verb", "http request method"))
-var HTTPBodyOption = RegisterOption(NewStringOptionType("body", "body of a http request"))
+var HTTPBodyOption = RegisterOption(flagsets.NewStringOptionType("body", "body of a http request"))
-var HTTPRedirectOption = RegisterOption(NewBoolOptionType("noredirect", "http redirect behavior"))
+var HTTPRedirectOption = RegisterOption(flagsets.NewBoolOptionType("noredirect", "http redirect behavior"))
// CommentOption .
-var CommentOption = RegisterOption(NewStringOptionType("comment", "comment field value"))
+var CommentOption = RegisterOption(flagsets.NewStringOptionType("comment", "comment field value"))
// ClassifierOption the optional classifier of a maven resource.
-var ClassifierOption = RegisterOption(NewStringOptionType("classifier", "maven classifier"))
+var ClassifierOption = RegisterOption(flagsets.NewStringOptionType("classifier", "maven classifier"))
// ExtensionOption the optional extension of a maven resource.
-var ExtensionOption = RegisterOption(NewStringOptionType("extension", "maven extension name"))
+var ExtensionOption = RegisterOption(flagsets.NewStringOptionType("extension", "maven extension name"))
// NPMRegistryOption sets the registry of the npm resource.
-var NPMRegistryOption = RegisterOption(NewStringOptionType("registry", "npm package registry"))
+var NPMRegistryOption = RegisterOption(flagsets.NewStringOptionType("registry", "npm package registry"))
// NPMPackageOption sets what package should be fetched from the npm registry.
-var NPMPackageOption = RegisterOption(NewStringOptionType("package", "npm package name"))
+var NPMPackageOption = PackageOption
// NPMVersionOption sets the version of the npm package.
-var NPMVersionOption = RegisterOption(NewStringOptionType("version", "npm package version"))
+var NPMVersionOption = RegisterOption(flagsets.NewStringOptionType("version", "npm package version"))
// IdPathOption is a path of identity specs.
-var IdPathOption = RegisterOption(NewStringArrayOptionType("idpath", "identity path (attr=value{,attr=value}"))
+var IdPathOption = RegisterOption(flagsets.NewStringArrayOptionType("idpath", "identity path (attr=value{,attr=value}"))
diff --git a/api/ocm/extensions/accessmethods/options/types.go b/api/ocm/extensions/accessmethods/options/types.go
deleted file mode 100644
index f02ecd03f7..0000000000
--- a/api/ocm/extensions/accessmethods/options/types.go
+++ /dev/null
@@ -1,125 +0,0 @@
-package options
-
-import (
- "fmt"
-
- "ocm.software/ocm/api/utils/cobrautils/flagsets"
-)
-
-type OptionType interface {
- flagsets.ConfigOptionType
- ValueType() string
- GetDescriptionText() string
-}
-
-type base = flagsets.ConfigOptionType
-
-type option struct {
- base
- valueType string
-}
-
-func (o *option) Equal(t flagsets.ConfigOptionType) bool {
- if ot, ok := t.(*option); ok {
- return o.valueType == ot.valueType && o.GetName() == ot.GetName()
- }
- return false
-}
-
-func (o *option) ValueType() string {
- return o.valueType
-}
-
-func (o *option) GetDescription() string {
- return fmt.Sprintf("[*%s*] %s", o.ValueType(), o.base.GetDescription())
-}
-
-func (o *option) GetDescriptionText() string {
- return o.base.GetDescription()
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
-func NewStringOptionType(name, desc string) OptionType {
- return &option{
- base: flagsets.NewStringOptionType(name, desc),
- valueType: TYPE_STRING,
- }
-}
-
-func NewStringArrayOptionType(name, desc string) OptionType {
- return &option{
- base: flagsets.NewStringArrayOptionType(name, desc),
- valueType: TYPE_STRINGARRAY,
- }
-}
-
-func NewIntOptionType(name, desc string) OptionType {
- return &option{
- base: flagsets.NewIntOptionType(name, desc),
- valueType: TYPE_INT,
- }
-}
-
-func NewBoolOptionType(name, desc string) OptionType {
- return &option{
- base: flagsets.NewBoolOptionType(name, desc),
- valueType: TYPE_BOOL,
- }
-}
-
-func NewYAMLOptionType(name, desc string) OptionType {
- return &option{
- base: flagsets.NewYAMLOptionType(name, desc),
- valueType: TYPE_YAML,
- }
-}
-
-func NewValueMapYAMLOptionType(name, desc string) OptionType {
- return &option{
- base: flagsets.NewValueMapYAMLOptionType(name, desc),
- valueType: TYPE_STRINGMAPYAML,
- }
-}
-
-func NewValueMapOptionType(name, desc string) OptionType {
- return &option{
- base: flagsets.NewValueMapOptionType(name, desc),
- valueType: TYPE_STRING2YAML,
- }
-}
-
-func NewStringMapOptionType(name, desc string) OptionType {
- return &option{
- base: flagsets.NewStringMapOptionType(name, desc),
- valueType: TYPE_STRING2STRING,
- }
-}
-
-func NewStringSliceMapOptionType(name, desc string) OptionType {
- return &option{
- base: flagsets.NewStringSliceMapOptionType(name, desc),
- valueType: TYPE_STRING2STRINGSLICE,
- }
-}
-
-func NewStringSliceMapColonOptionType(name, desc string) OptionType {
- return &option{
- base: flagsets.NewStringSliceMapColonOptionType(name, desc),
- valueType: TYPE_STRINGCOLONSTRINGSLICE,
- }
-}
-
-func NewBytesOptionType(name, desc string) OptionType {
- return &option{
- base: flagsets.NewBytesOptionType(name, desc),
- valueType: TYPE_BYTES,
- }
-}
-
-func NewIdentityPathOptionType(name, desc string) OptionType {
- return &option{
- base: flagsets.NewIdentityPathOptionType(name, desc),
- valueType: TYPE_IDENTITYPATH,
- }
-}
diff --git a/api/ocm/extensions/accessmethods/plugin/method.go b/api/ocm/extensions/accessmethods/plugin/method.go
index 95f8e32eed..9dede0b16b 100644
--- a/api/ocm/extensions/accessmethods/plugin/method.go
+++ b/api/ocm/extensions/accessmethods/plugin/method.go
@@ -36,6 +36,10 @@ func (s *AccessSpec) Describe(ctx cpi.Context) string {
return s.handler.Describe(s, ctx)
}
+func (s *AccessSpec) Info(ctx cpi.Context) *cpi.UniformAccessSpecInfo {
+ return s.handler.Info(s, ctx)
+}
+
func (_ *AccessSpec) IsLocal(cpi.Context) bool {
return false
}
diff --git a/api/ocm/extensions/accessmethods/plugin/plugin.go b/api/ocm/extensions/accessmethods/plugin/plugin.go
index a6465a7224..5e70200d57 100644
--- a/api/ocm/extensions/accessmethods/plugin/plugin.go
+++ b/api/ocm/extensions/accessmethods/plugin/plugin.go
@@ -33,7 +33,7 @@ func NewPluginHandler(p plugin.Plugin) *PluginHandler {
return &PluginHandler{plug: p}
}
-func (p *PluginHandler) Info(spec *AccessSpec) (*ppi.AccessSpecInfo, error) {
+func (p *PluginHandler) GetAccessSpecInfo(spec *AccessSpec) (*ppi.AccessSpecInfo, error) {
if p.info != nil || p.err != nil {
raw, err := spec.UnstructuredVersionedTypedObject.GetRaw()
if err != nil {
@@ -58,7 +58,7 @@ func (p *PluginHandler) AccessMethod(spec *AccessSpec, cv cpi.ComponentVersionAc
return nil, err
}
- info, err := p.Info(spec)
+ info, err := p.GetAccessSpecInfo(spec)
if err != nil {
return nil, err
}
@@ -66,7 +66,7 @@ func (p *PluginHandler) AccessMethod(spec *AccessSpec, cv cpi.ComponentVersionAc
}
func (p *PluginHandler) getCredentialData(spec *AccessSpec, cv cpi.ComponentVersionAccess) (json.RawMessage, error) {
- info, err := p.Info(spec)
+ info, err := p.GetAccessSpecInfo(spec)
if err != nil {
return nil, err
}
@@ -94,23 +94,45 @@ func (p *PluginHandler) Describe(spec *AccessSpec, ctx cpi.Context) string {
if mspec == nil {
return "unknown type " + spec.GetType()
}
- info, err := p.Info(spec)
+ info, err := p.GetAccessSpecInfo(spec)
if err != nil {
return err.Error()
}
return info.Short
}
+func (p *PluginHandler) Info(spec *AccessSpec, ctx cpi.Context) *cpi.UniformAccessSpecInfo {
+ mspec := p.GetAccessMethodDescriptor(spec.GetKind(), spec.GetVersion())
+ if mspec == nil {
+ return &cpi.UniformAccessSpecInfo{
+ Kind: spec.GetKind(),
+ }
+ }
+ info, err := p.GetAccessSpecInfo(spec)
+ if err != nil || info.Info == nil {
+ return &cpi.UniformAccessSpecInfo{
+ Kind: spec.GetKind(),
+ }
+ }
+ return &cpi.UniformAccessSpecInfo{
+ Kind: spec.GetKind(),
+ Host: info.Info.Host,
+ Port: info.Info.Port,
+ Path: info.Info.Path,
+ Info: info.Info.Info,
+ }
+}
+
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)
+ info, err := p.GetAccessSpecInfo(spec)
if err != nil {
return ""
}
- return info.Short
+ return info.MediaType
}
func (p *PluginHandler) GetReferenceHint(spec *AccessSpec, cv cpi.ComponentVersionAccess) string {
@@ -118,7 +140,7 @@ func (p *PluginHandler) GetReferenceHint(spec *AccessSpec, cv cpi.ComponentVersi
if mspec == nil {
return "unknown type " + spec.GetType()
}
- info, err := p.Info(spec)
+ info, err := p.GetAccessSpecInfo(spec)
if err != nil {
return ""
}
diff --git a/api/ocm/extensions/accessmethods/relativeociref/method.go b/api/ocm/extensions/accessmethods/relativeociref/method.go
index 69a28664a9..9d0d3d5594 100644
--- a/api/ocm/extensions/accessmethods/relativeociref/method.go
+++ b/api/ocm/extensions/accessmethods/relativeociref/method.go
@@ -44,6 +44,13 @@ func (a *AccessSpec) Describe(context accspeccpi.Context) string {
return fmt.Sprintf("local OCI artifact %s", a.Reference)
}
+func (a *AccessSpec) Info(ctx accspeccpi.Context) *accspeccpi.UniformAccessSpecInfo {
+ return &accspeccpi.UniformAccessSpecInfo{
+ Kind: Type,
+ Info: a.Reference,
+ }
+}
+
func (a *AccessSpec) IsLocal(context accspeccpi.Context) bool {
return true
}
diff --git a/api/ocm/extensions/accessmethods/s3/method.go b/api/ocm/extensions/accessmethods/s3/method.go
index b455ab9f0e..5bce32c0b9 100644
--- a/api/ocm/extensions/accessmethods/s3/method.go
+++ b/api/ocm/extensions/accessmethods/s3/method.go
@@ -85,9 +85,19 @@ func (a AccessSpec) MarshalJSON() ([]byte, error) {
}
func (a *AccessSpec) Describe(ctx accspeccpi.Context) string {
+ if a.Version != "" {
+ return fmt.Sprintf("S3 key %s(%s) in bucket %s", a.Key, a.Version, a.Bucket)
+ }
return fmt.Sprintf("S3 key %s in bucket %s", a.Key, a.Bucket)
}
+func (a *AccessSpec) Info(ctx accspeccpi.Context) *accspeccpi.UniformAccessSpecInfo {
+ return &accspeccpi.UniformAccessSpecInfo{
+ Kind: Type,
+ Info: a.Bucket + "/" + a.Key,
+ }
+}
+
func (_ *AccessSpec) IsLocal(accspeccpi.Context) bool {
return false
}
diff --git a/api/ocm/extensions/accessmethods/wget/method.go b/api/ocm/extensions/accessmethods/wget/method.go
index 6196ed0410..8f314e7f95 100644
--- a/api/ocm/extensions/accessmethods/wget/method.go
+++ b/api/ocm/extensions/accessmethods/wget/method.go
@@ -3,6 +3,7 @@ package wget
import (
"fmt"
"io"
+ "net/url"
"sync"
"github.com/mandelsoft/goutils/optionutils"
@@ -70,6 +71,19 @@ func (a *AccessSpec) Describe(ctx accspeccpi.Context) string {
return fmt.Sprintf("Files from %s", a.URL)
}
+func (a *AccessSpec) Info(ctx accspeccpi.Context) *accspeccpi.UniformAccessSpecInfo {
+ u, err := url.Parse(a.URL)
+ if err != nil {
+ u = &url.URL{}
+ }
+ return &accspeccpi.UniformAccessSpecInfo{
+ Kind: Type,
+ Host: u.Hostname(),
+ Port: u.Port(),
+ Path: u.Path,
+ }
+}
+
func (a *AccessSpec) IsLocal(ctx accspeccpi.Context) bool {
return false
}
diff --git a/api/ocm/extensions/labels/routingslip/internal/entrytypes.go b/api/ocm/extensions/labels/routingslip/internal/entrytypes.go
index dbb7f42051..f70f325b58 100644
--- a/api/ocm/extensions/labels/routingslip/internal/entrytypes.go
+++ b/api/ocm/extensions/labels/routingslip/internal/entrytypes.go
@@ -41,18 +41,18 @@ type (
////////////////////////////////////////////////////////////////////////////////
-type EntryTypeScheme = flagsetscheme.ExtendedTypeScheme[Entry, EntryType, flagsets.ExplicitlyTypedConfigTypeOptionSetConfigProvider]
+type EntryTypeScheme = flagsetscheme.ExtendedProviderTypeScheme[Entry, EntryType, flagsets.ExplicitlyTypedConfigTypeOptionSetConfigProvider]
-func unwrapTypeScheme(s EntryTypeScheme) flagsetscheme.TypeScheme[Entry, EntryType] {
+func unwrapTypeScheme(s EntryTypeScheme) flagsetscheme.VersionedTypeScheme[Entry, EntryType] {
return s.Unwrap()
}
func NewEntryTypeScheme(base ...EntryTypeScheme) EntryTypeScheme {
- return flagsetscheme.NewTypeSchemeWrapper[Entry, EntryType, flagsets.ExplicitlyTypedConfigTypeOptionSetConfigProvider](flagsetscheme.NewTypeScheme[Entry, EntryType, flagsetscheme.TypeScheme[Entry, EntryType]]("Entry type", "entry", "", "routing slip entry specification", "Entry Specification Options", &UnknownEntry{}, true, sliceutils.Transform(base, unwrapTypeScheme)...))
+ return flagsetscheme.NewVersionedTypeSchemeWrapper[Entry, EntryType, flagsets.ExplicitlyTypedConfigTypeOptionSetConfigProvider](flagsetscheme.NewVersionedTypeScheme[Entry, EntryType, flagsetscheme.VersionedTypeScheme[Entry, EntryType]]("Entry type", "entry", "", "routing slip entry specification", "Entry Specification Options", &UnknownEntry{}, true, sliceutils.Transform(base, unwrapTypeScheme)...))
}
func NewStrictEntryTypeScheme(base ...EntryTypeScheme) EntryTypeScheme {
- return flagsetscheme.NewTypeSchemeWrapper[Entry, EntryType, flagsets.ExplicitlyTypedConfigTypeOptionSetConfigProvider](flagsetscheme.NewTypeScheme[Entry, EntryType, flagsetscheme.TypeScheme[Entry, EntryType]]("Entry type", "entry", "", "routing slip entry specification", "Entry Specification Options", nil, false, sliceutils.Transform(base, unwrapTypeScheme)...))
+ return flagsetscheme.NewVersionedTypeSchemeWrapper[Entry, EntryType, flagsets.ExplicitlyTypedConfigTypeOptionSetConfigProvider](flagsetscheme.NewVersionedTypeScheme[Entry, EntryType, flagsetscheme.VersionedTypeScheme[Entry, EntryType]]("Entry type", "entry", "", "routing slip entry specification", "Entry Specification Options", nil, false, sliceutils.Transform(base, unwrapTypeScheme)...))
}
func CreateEntry(t runtime.TypedObject) (Entry, error) {
diff --git a/api/ocm/extensions/labels/routingslip/spi/support.go b/api/ocm/extensions/labels/routingslip/spi/support.go
index b0e50ca240..4dc95f32c4 100644
--- a/api/ocm/extensions/labels/routingslip/spi/support.go
+++ b/api/ocm/extensions/labels/routingslip/spi/support.go
@@ -26,15 +26,15 @@ func MustNewEntryMultiFormatVersion(kind string, formats EntryFormatVersionRegis
////////////////////////////////////////////////////////////////////////////////
func NewEntryType[I Entry](name string, opts ...EntryTypeOption) EntryType {
- return flagsetscheme.NewTypedObjectTypeObject[Entry](runtime.NewVersionedTypedObjectType[Entry, I](name), opts...)
+ return flagsetscheme.NewVersionedTypedObjectTypeObject[Entry](runtime.NewVersionedTypedObjectType[Entry, I](name), opts...)
}
func NewEntryTypeByConverter[I Entry, V runtime.VersionedTypedObject](name string, converter runtime.Converter[I, V], opts ...EntryTypeOption) EntryType {
- return flagsetscheme.NewTypedObjectTypeObject[Entry](runtime.NewVersionedTypedObjectTypeByConverter[Entry, I, V](name, converter), opts...)
+ return flagsetscheme.NewVersionedTypedObjectTypeObject[Entry](runtime.NewVersionedTypedObjectTypeByConverter[Entry, I, V](name, converter), opts...)
}
func NewEntryTypeByFormatVersion(name string, fmt runtime.FormatVersion[Entry], opts ...EntryTypeOption) EntryType {
- return flagsetscheme.NewTypedObjectTypeObject[Entry](runtime.NewVersionedTypedObjectTypeByFormatVersion[Entry](name, fmt), opts...)
+ return flagsetscheme.NewVersionedTypedObjectTypeObject[Entry](runtime.NewVersionedTypedObjectTypeByFormatVersion[Entry](name, fmt), opts...)
}
////////////////////////////////////////////////////////////////////////////////
diff --git a/api/ocm/extensions/pubsub/interface.go b/api/ocm/extensions/pubsub/interface.go
index 744a14a82a..f7fc402f14 100644
--- a/api/ocm/extensions/pubsub/interface.go
+++ b/api/ocm/extensions/pubsub/interface.go
@@ -9,7 +9,6 @@ import (
"github.com/mandelsoft/goutils/errors"
"github.com/mandelsoft/goutils/general"
"github.com/mandelsoft/goutils/generics"
- "github.com/mandelsoft/goutils/optionutils"
"github.com/modern-go/reflect2"
"ocm.software/ocm/api/ocm/cpi"
@@ -33,7 +32,7 @@ func WithDesciption(desc string) Option {
////////////////////////////////////////////////////////////////////////////////
-type PubSubType descriptivetype.TypedObjectType[PubSubSpec]
+type PubSubType descriptivetype.VersionedTypedObjectType[PubSubSpec]
// PubSubSpec is the interface publish/subscribe specifications
// must fulfill. The main task is to map the specification
@@ -61,14 +60,14 @@ type PubSubMethod interface {
// PubSub types. A PubSub type is finally able to
// provide an implementation for notifying a dedicated
// PubSub instance.
-type TypeScheme descriptivetype.TypeScheme[PubSubSpec, PubSubType]
+type TypeScheme descriptivetype.VersionedTypeScheme[PubSubSpec, PubSubType]
func NewTypeScheme(base ...TypeScheme) TypeScheme {
- return descriptivetype.NewTypeScheme[PubSubSpec, PubSubType, TypeScheme]("PubSub type", nil, &UnknownPubSubSpec{}, false, base...)
+ return descriptivetype.NewVersionedTypeScheme[PubSubSpec, PubSubType, TypeScheme]("PubSub type", nil, &UnknownPubSubSpec{}, false, base...)
}
func NewStrictTypeScheme(base ...TypeScheme) runtime.VersionedTypeRegistry[PubSubSpec, PubSubType] {
- return descriptivetype.NewTypeScheme[PubSubSpec, PubSubType, TypeScheme]("PubSub type", nil, &UnknownPubSubSpec{}, false, base...)
+ return descriptivetype.NewVersionedTypeScheme[PubSubSpec, PubSubType, TypeScheme]("PubSub type", nil, &UnknownPubSubSpec{}, false, base...)
}
// DefaultTypeScheme contains all globally known PubSub serializers.
@@ -83,10 +82,7 @@ func CreatePubSubSpec(t runtime.TypedObject) (PubSubSpec, error) {
}
func NewPubSubType[I PubSubSpec](name string, opts ...Option) PubSubType {
- t := descriptivetype.NewTypedObjectTypeObject[PubSubSpec](runtime.NewVersionedTypedObjectType[PubSubSpec, I](name))
- ta := descriptivetype.NewTypeObjectTarget[PubSubSpec](t)
- optionutils.ApplyOptions[descriptivetype.OptionTarget](ta, opts...)
- return t
+ return descriptivetype.NewVersionedTypedObjectTypeObject[PubSubSpec](runtime.NewVersionedTypedObjectType[PubSubSpec, I](name), opts...)
}
////////////////////////////////////////////////////////////////////////////////
diff --git a/api/ocm/interface.go b/api/ocm/interface.go
index b4c5f93d89..6f90298409 100644
--- a/api/ocm/interface.go
+++ b/api/ocm/interface.go
@@ -48,6 +48,7 @@ type (
ComponentLister = internal.ComponentLister
ComponentAccess = internal.ComponentAccess
ComponentVersionAccess = internal.ComponentVersionAccess
+ UniformAccessSpecInfo = internal.UniformAccessSpecInfo
AccessSpec = internal.AccessSpec
GenericAccessSpec = internal.GenericAccessSpec
HintProvider = internal.HintProvider
diff --git a/api/ocm/internal/accessspecref.go b/api/ocm/internal/accessspecref.go
index 0a96071b11..ab5d0545e3 100644
--- a/api/ocm/internal/accessspecref.go
+++ b/api/ocm/internal/accessspecref.go
@@ -79,6 +79,16 @@ func (a *AccessSpecRef) Describe(ctx Context) string {
return "invalid access specification"
}
+func (a *AccessSpecRef) Info(ctx Context) *UniformAccessSpecInfo {
+ a.assure(ctx)
+ if a.evaluated != nil {
+ return a.evaluated.Info(ctx)
+ }
+ return &UniformAccessSpecInfo{
+ Kind: a.GetKind(),
+ }
+}
+
func (a *AccessSpecRef) GetType() string {
if a.evaluated != nil {
return a.evaluated.GetType()
diff --git a/api/ocm/internal/accesstypes.go b/api/ocm/internal/accesstypes.go
index 42f87e6891..8b1c861882 100644
--- a/api/ocm/internal/accesstypes.go
+++ b/api/ocm/internal/accesstypes.go
@@ -12,6 +12,7 @@ import (
"ocm.software/ocm/api/ocm/compdesc"
metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1"
+ "ocm.software/ocm/api/tech"
"ocm.software/ocm/api/utils/cobrautils/flagsets/flagsetscheme"
"ocm.software/ocm/api/utils/errkind"
"ocm.software/ocm/api/utils/refmgmt"
@@ -20,6 +21,8 @@ import (
type AccessType flagsetscheme.VersionTypedObjectType[AccessSpec]
+type UniformAccessSpecInfo = tech.UniformAccessSpecInfo
+
// AccessSpec is the interface access method specifications
// must fulfill. The main task is to map the specification
// to a concrete implementation of the access method for a dedicated
@@ -27,6 +30,7 @@ type AccessType flagsetscheme.VersionTypedObjectType[AccessSpec]
type AccessSpec interface {
compdesc.AccessSpec
Describe(Context) string
+ Info(ctx Context) *UniformAccessSpecInfo
IsLocal(Context) bool
GlobalAccessSpec(Context) AccessSpec
// AccessMethod provides an access method implementation for
@@ -102,14 +106,14 @@ type AccessMethod interface {
AsBlobAccess() BlobAccess
}
-type AccessTypeScheme flagsetscheme.TypeScheme[AccessSpec, AccessType]
+type AccessTypeScheme flagsetscheme.VersionedTypeScheme[AccessSpec, AccessType]
func NewAccessTypeScheme(base ...AccessTypeScheme) AccessTypeScheme {
- return flagsetscheme.NewTypeScheme[AccessSpec, AccessType, AccessTypeScheme]("Access type", "access", "accessType", "blob access specification", "Access Specification Options", &UnknownAccessSpec{}, true, base...)
+ return flagsetscheme.NewVersionedTypeScheme[AccessSpec, AccessType, AccessTypeScheme]("Access type", "access", "accessType", "blob access specification", "Access Specification Options", &UnknownAccessSpec{}, true, base...)
}
func NewStrictAccessTypeScheme(base ...AccessTypeScheme) runtime.VersionedTypeRegistry[AccessSpec, AccessType] {
- return flagsetscheme.NewTypeScheme[AccessSpec, AccessType, AccessTypeScheme]("Access type", "access", "accessType", "blob access specification", "Access Specification Options", nil, false, base...)
+ return flagsetscheme.NewVersionedTypeScheme[AccessSpec, AccessType, AccessTypeScheme]("Access type", "access", "accessType", "blob access specification", "Access Specification Options", nil, false, base...)
}
// DefaultAccessTypeScheme contains all globally known access serializer.
@@ -146,6 +150,13 @@ func (s *UnknownAccessSpec) Describe(ctx Context) string {
return fmt.Sprintf("unknown access method type %q", s.GetType())
}
+func (s *UnknownAccessSpec) Info(ctx Context) *UniformAccessSpecInfo {
+ return &UniformAccessSpecInfo{
+ Kind: s.GetKind(),
+ Info: fmt.Sprintf("unknown access method type %q", s.GetType()),
+ }
+}
+
func (_ *UnknownAccessSpec) IsLocal(Context) bool {
return false
}
@@ -207,6 +218,16 @@ func (s *GenericAccessSpec) Describe(ctx Context) string {
return eff.Describe(ctx)
}
+func (s *GenericAccessSpec) Info(ctx Context) *UniformAccessSpecInfo {
+ eff, err := s.Evaluate(ctx)
+ if err != nil {
+ return &UniformAccessSpecInfo{
+ Kind: s.GetType(),
+ }
+ }
+ return eff.Info(ctx)
+}
+
func (s *GenericAccessSpec) Evaluate(ctx Context) (AccessSpec, error) {
raw, err := s.GetRaw()
if err != nil {
diff --git a/api/ocm/internal/uniform.go b/api/ocm/internal/uniform.go
index 3aea8d8aaa..ef694aa128 100644
--- a/api/ocm/internal/uniform.go
+++ b/api/ocm/internal/uniform.go
@@ -3,6 +3,7 @@ package internal
import (
"encoding/json"
"fmt"
+ "strings"
"sync"
"github.com/mandelsoft/goutils/errors"
@@ -47,6 +48,14 @@ func (r *UniformRepositorySpec) CredHost() string {
return r.Host
}
+func (u *UniformRepositorySpec) HostPort() (string, string) {
+ i := strings.Index(u.Host, ":")
+ if i < 0 {
+ return u.Host, ""
+ }
+ return u.Host[:i], u.Host[i+1:]
+}
+
func (u *UniformRepositorySpec) String() string {
t := u.Type
if t != "" {
diff --git a/api/ocm/plugin/cache/plugin.go b/api/ocm/plugin/cache/plugin.go
index f6412b7a23..69d85918d7 100644
--- a/api/ocm/plugin/cache/plugin.go
+++ b/api/ocm/plugin/cache/plugin.go
@@ -131,6 +131,30 @@ func (p *pluginImpl) GetLabelMergeSpecification(name, version string) *descripto
return fallback
}
+func (p *pluginImpl) GetInputTypeDescriptor(name, version string) *descriptor.InputTypeDescriptor {
+ if !p.IsValid() {
+ return nil
+ }
+
+ var fallback descriptor.InputTypeDescriptor
+ fallbackFound := false
+ for _, i := range p.descriptor.Inputs {
+ if i.Name == name {
+ if i.Version == version {
+ return &i
+ }
+ if i.Version == "" || i.Version == "v1" {
+ fallback = i
+ fallbackFound = true
+ }
+ }
+ }
+ if fallbackFound && (version == "" || version == "v1") {
+ return &fallback
+ }
+ return nil
+}
+
func (p *pluginImpl) GetAccessMethodDescriptor(name, version string) *descriptor.AccessMethodDescriptor {
if !p.IsValid() {
return nil
@@ -232,6 +256,22 @@ func (p *pluginImpl) GetUploaderDescriptor(name string) *descriptor.UploaderDesc
return p.descriptor.Uploaders.Get(name)
}
+func (p *pluginImpl) GetTransferHandler(name string) *descriptor.TransferHandlerDescriptor {
+ if !p.IsValid() {
+ return nil
+ }
+
+ return p.descriptor.TransferHandlers.Get(name)
+}
+
+func (p *pluginImpl) GetTransferHandlerNames() []string {
+ if !p.IsValid() {
+ return nil
+ }
+
+ return p.descriptor.TransferHandlers.GetNames()
+}
+
func (p *pluginImpl) Message() string {
if p.IsValid() {
return p.descriptor.Short
diff --git a/api/ocm/plugin/cache/plugindir.go b/api/ocm/plugin/cache/plugindir.go
index a09d1b0bda..54faba5b66 100644
--- a/api/ocm/plugin/cache/plugindir.go
+++ b/api/ocm/plugin/cache/plugindir.go
@@ -118,7 +118,7 @@ func (c *pluginDirImpl) scan(path string) error {
errmsg = err.Error()
} else {
if desc.PluginName != fi.Name() {
- errmsg = fmt.Sprintf("nmatching plugin name %q", desc.PluginName)
+ errmsg = fmt.Sprintf("no matching plugin name %q, found file %q", desc.PluginName, fi.Name())
}
}
c.add(fi.Name(), desc, execpath, errmsg, list)
diff --git a/api/ocm/plugin/common/describe.go b/api/ocm/plugin/common/describe.go
index fa6c0e3165..09586658f8 100644
--- a/api/ocm/plugin/common/describe.go
+++ b/api/ocm/plugin/common/describe.go
@@ -5,6 +5,8 @@ import (
"strings"
"github.com/Masterminds/semver/v3"
+ "github.com/mandelsoft/goutils/generics"
+ "github.com/mandelsoft/goutils/maputils"
"github.com/mandelsoft/goutils/set"
"golang.org/x/exp/slices"
@@ -12,6 +14,7 @@ import (
"ocm.software/ocm/api/ocm/extensions/accessmethods/options"
"ocm.software/ocm/api/ocm/plugin/descriptor"
"ocm.software/ocm/api/utils"
+ "ocm.software/ocm/api/utils/cobrautils/flagsets"
common "ocm.software/ocm/api/utils/misc"
"ocm.software/ocm/api/utils/semverutils"
)
@@ -80,6 +83,16 @@ func DescribePluginDescriptorCapabilities(reg api.ActionTypeRegistry, d *descrip
out.Printf("Config Types for CLI Command Extensions:\n")
DescribeConfigTypes(d, out)
}
+ if len(d.TransferHandlers) > 0 {
+ out.Printf("\n")
+ out.Printf("Transfer Handlers:\n")
+ DescribeTransferHandlers(d, out)
+ }
+ if len(d.SigningHandlers) > 0 {
+ out.Printf("\n")
+ out.Printf("Signing Handlers:\n")
+ DescribeSigningHandlers(d, out)
+ }
}
type MethodInfo struct {
@@ -91,7 +104,7 @@ type MethodInfo struct {
type MethodVersion struct {
Name string
Format string
- Options map[string]options.OptionType
+ Options map[string]flagsets.ConfigOptionType
}
func GetAccessMethodInfo(methods []descriptor.AccessMethodDescriptor) map[string]*MethodInfo {
@@ -117,7 +130,7 @@ func GetAccessMethodInfo(methods []descriptor.AccessMethodDescriptor) map[string
if v == nil {
v = &MethodVersion{
Name: vers,
- Options: map[string]options.OptionType{},
+ Options: map[string]flagsets.ConfigOptionType{},
}
i.Versions[vers] = v
}
@@ -316,7 +329,7 @@ type ValueSetInfo struct {
type ValueSetVersion struct {
Name string
Format string
- Options map[string]options.OptionType
+ Options map[string]flagsets.ConfigOptionType
}
func GetValueSetInfo(valuesets []descriptor.ValueSetDescriptor) map[string]*ValueSetInfo {
@@ -345,7 +358,7 @@ func GetValueSetInfo(valuesets []descriptor.ValueSetDescriptor) map[string]*Valu
if v == nil {
v = &ValueSetVersion{
Name: vers,
- Options: map[string]options.OptionType{},
+ Options: map[string]flagsets.ConfigOptionType{},
}
i.Versions[vers] = v
}
@@ -517,6 +530,102 @@ func DescribeConfigTypes(d *descriptor.Descriptor, out common.Printer) {
}
}
+type TransferHandlerInfo struct {
+ Name string
+ Description string
+ Questions map[string]descriptor.QuestionDescriptor
+}
+
+func GetTransferHandlerInfo(types []descriptor.TransferHandlerDescriptor) map[string]*TransferHandlerInfo {
+ found := map[string]*TransferHandlerInfo{}
+ for _, m := range types {
+ i := found[m.Name]
+ if i == nil {
+ i := &TransferHandlerInfo{
+ Name: m.Name,
+ Description: m.Description,
+ Questions: map[string]descriptor.QuestionDescriptor{},
+ }
+ for _, q := range m.Questions {
+ i.Questions[q.Question] = q
+ }
+ found[m.Name] = i
+ }
+ }
+ return found
+}
+
+func DescribeTransferHandlers(d *descriptor.Descriptor, out common.Printer) {
+ types := GetTransferHandlerInfo(d.TransferHandlers)
+ for _, n := range utils.StringMapKeys(types) {
+ out.Printf("- Name: %s\n", n)
+ m := types[n]
+ if m.Description != "" {
+ out.Printf("%s\n", utils.IndentLines(m.Description, " "))
+ }
+ out := out.AddGap(" ")
+ out.Printf("Questions:\n")
+ for _, qn := range utils.StringMapKeys(m.Questions) {
+ desc := TransferHandlerQuestions[qn]
+ if desc == "" {
+ desc = "invalid question"
+ }
+ out.Printf("- Name: %s (%s)\n", qn, desc)
+ q := m.Questions[qn]
+ if q.Description != "" {
+ out.Printf("%s\n", utils.IndentLines(q.Description, " "))
+ }
+ out := out.AddGap(" ")
+ if q.Labels == nil {
+ out.Printf("consumes all labels\n")
+ } else {
+ if len(*q.Labels) == 0 {
+ out.Printf("consumes no labels\n")
+ } else {
+ labels := slices.Clone(*q.Labels)
+ sort.Strings(labels)
+ out.Printf("Labels:\n")
+ for _, l := range labels {
+ out.Printf("- %s\n", l)
+ }
+ }
+ }
+ }
+ }
+}
+
+func GetSigningInfo(types []descriptor.SigningHandlerDescriptor) map[string]*descriptor.SigningHandlerDescriptor {
+ found := map[string]*descriptor.SigningHandlerDescriptor{}
+ for _, m := range types {
+ i := found[m.Name]
+ if i == nil {
+ found[m.Name] = generics.Pointer(m)
+ }
+ }
+ return found
+}
+
+func DescribeSigningHandlers(d *descriptor.Descriptor, out common.Printer) {
+ handlers := GetSigningInfo(d.SigningHandlers)
+ for _, n := range maputils.OrderedKeys(handlers) {
+ out.Printf("- Name: %s\n", n)
+ m := handlers[n]
+ if m.Description != "" {
+ out.Printf("%s\n", utils.IndentLines(m.Description, " "))
+ }
+ out := out.AddGap(" ")
+ out.Printf("Credentials: %T\n", m.Credentials)
+ out.Printf("Signer: %T\n", m.Signer)
+ out.Printf("Verifier: %T\n", m.Verifier)
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+func GetName[T descriptor.Named](n T) string {
+ return n.GetName()
+}
+
type Describable interface {
Describe() string
}
diff --git a/api/ocm/plugin/common/questions.go b/api/ocm/plugin/common/questions.go
new file mode 100644
index 0000000000..a2a7171990
--- /dev/null
+++ b/api/ocm/plugin/common/questions.go
@@ -0,0 +1,23 @@
+package common
+
+import (
+ "ocm.software/ocm/api/ocm/plugin/internal"
+)
+
+const (
+ Q_UPDATE_VERSION = internal.Q_UPDATE_VERSION
+ Q_OVERWRITE_VERSION = internal.Q_OVERWRITE_VERSION
+ Q_ENFORCE_TRANSPORT = internal.Q_ENFORCE_TRANSPORT
+ Q_TRANSFER_VERSION = internal.Q_TRANSFER_VERSION
+ Q_TRANSFER_RESOURCE = internal.Q_TRANSFER_RESOURCE
+ Q_TRANSFER_SOURCE = internal.Q_TRANSFER_SOURCE
+)
+
+var TransferHandlerQuestions = map[string]string{
+ Q_UPDATE_VERSION: "update non-signature relevant parts",
+ Q_ENFORCE_TRANSPORT: "enforce transport as component version does not exist in target ",
+ Q_OVERWRITE_VERSION: "update signature relevant parts",
+ Q_TRANSFER_VERSION: "decide on updating a component version",
+ Q_TRANSFER_RESOURCE: "value transport for resources",
+ Q_TRANSFER_SOURCE: "value transport for sources",
+}
diff --git a/api/ocm/plugin/descriptor/const.go b/api/ocm/plugin/descriptor/const.go
index d2becd18a5..54fc87e456 100644
--- a/api/ocm/plugin/descriptor/const.go
+++ b/api/ocm/plugin/descriptor/const.go
@@ -7,13 +7,17 @@ import (
)
const (
- KIND_PLUGIN = "plugin"
- KIND_DOWNLOADER = "downloader"
- KIND_UPLOADER = "uploader"
- KIND_ACCESSMETHOD = errkind.KIND_ACCESSMETHOD
- KIND_ACTION = action.KIND_ACTION
- KIND_VALUESET = "value set"
- KIND_PURPOSE = "purposet"
+ 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"
+ KIND_QUESTION = "question"
+ KIND_SIGNING_HANDLER = "signing handler"
+ KIND_VALUESET = "value set"
+ KIND_PURPOSE = "purposet"
)
var REALM = ocmlog.DefineSubRealm("OCM plugin handling", "plugins")
diff --git a/api/ocm/plugin/descriptor/descriptor.go b/api/ocm/plugin/descriptor/descriptor.go
index 3a371040c6..d4a293203a 100644
--- a/api/ocm/plugin/descriptor/descriptor.go
+++ b/api/ocm/plugin/descriptor/descriptor.go
@@ -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"`
@@ -24,6 +25,8 @@ type Descriptor struct {
LabelMergeSpecifications List[LabelMergeSpecification] `json:"labelMergeSpecifications,omitempty"`
ValueSets List[ValueSetDescriptor] `json:"valuesets,omitempty"`
Commands List[CommandDescriptor] `json:"commands,omitempty"`
+ TransferHandlers List[TransferHandlerDescriptor] `json:"transferHandlers,omitempty"`
+ SigningHandlers List[SigningHandlerDescriptor] `json:"signingHandlers,omitempty"`
ConfigTypes List[ConfigTypeDescriptor] `json:"configTypes,omitempty"`
}
@@ -58,6 +61,12 @@ func (d *Descriptor) Capabilities() []string {
if len(d.ConfigTypes) > 0 {
caps = append(caps, "Config Types")
}
+ if len(d.TransferHandlers) > 0 {
+ caps = append(caps, "Transfer Handlers")
+ }
+ if len(d.SigningHandlers) > 0 {
+ caps = append(caps, "Signing Handlers")
+ }
return caps
}
@@ -117,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"`
@@ -235,6 +252,54 @@ func (a CommandDescriptor) GetDescription() string {
////////////////////////////////////////////////////////////////////////////////
+type TransferHandlerDescriptor struct {
+ Name string `json:"name"`
+ Description string `json:"description,omitempty"`
+ Questions []QuestionDescriptor `json:"questions,omitempty"`
+}
+
+func (d TransferHandlerDescriptor) GetName() string {
+ return d.Name
+}
+
+func (d TransferHandlerDescriptor) GetQuestion(name string) *QuestionDescriptor {
+ for _, q := range d.Questions {
+ if q.Question == name {
+ return &q
+ }
+ }
+ return nil
+}
+
+type QuestionDescriptor struct {
+ // Question is the name of the question the plugin can answer.
+ // Possible types and their meaning is described by the
+ // variable common.TransferHandlerQuestions.
+ Question string `json:"question"`
+ Description string `json:"description,omitempty"`
+ // Labels described the list of labels passed to the
+ // plugin if they exist. If not specified all labels
+ // are transferred, if an empty list is specified no
+ // labels are transferred to the plugin command.
+ Labels *[]string `json:"labels,omitempty"`
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+type SigningHandlerDescriptor struct {
+ Name string `json:"name"`
+ Description string `json:"description,omitempty"`
+ Credentials bool `json:"credentials,omitempty"`
+ Signer bool `json:"signer,omitempty"`
+ Verifier bool `json:"verifier,omitempty"`
+}
+
+func (d SigningHandlerDescriptor) GetName() string {
+ return d.Name
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
type ConfigTypeDescriptor = ValueTypeDefinition
////////////////////////////////////////////////////////////////////////////////
diff --git a/api/ocm/plugin/interface.go b/api/ocm/plugin/interface.go
index 4d4091179e..ce2278495c 100644
--- a/api/ocm/plugin/interface.go
+++ b/api/ocm/plugin/interface.go
@@ -3,13 +3,16 @@ package plugin
import (
"ocm.software/ocm/api/ocm/plugin/descriptor"
"ocm.software/ocm/api/ocm/plugin/internal"
+ "ocm.software/ocm/api/tech/signing"
)
const (
- KIND_PLUGIN = descriptor.KIND_PLUGIN
- KIND_UPLOADER = descriptor.KIND_UPLOADER
- KIND_ACCESSMETHOD = descriptor.KIND_ACCESSMETHOD
- KIND_ACTION = descriptor.KIND_ACTION
+ KIND_PLUGIN = descriptor.KIND_PLUGIN
+ KIND_UPLOADER = descriptor.KIND_UPLOADER
+ KIND_ACCESSMETHOD = descriptor.KIND_ACCESSMETHOD
+ KIND_INPUTTYPE = descriptor.KIND_INPUTTYPE
+ KIND_ACTION = descriptor.KIND_ACTION
+ KIND_TRANSFERHANDLER = descriptor.KIND_TRANSFERHANDLER
)
var TAG = descriptor.REALM
@@ -18,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
@@ -29,5 +33,40 @@ type (
CommandDescriptor = descriptor.CommandDescriptor
AccessSpecInfo = internal.AccessSpecInfo
+ InputSpecInfo = internal.InputSpecInfo
UploadTargetSpecInfo = internal.UploadTargetSpecInfo
+
+ SignatureSpec = internal.SignatureSpec
+)
+
+func SignatureSpecFor(sig *signing.Signature) *SignatureSpec {
+ return internal.SignatureSpecFor(sig)
+}
+
+//
+// Transfer handler types and constants
+//
+
+const (
+ Q_UPDATE_VERSION = internal.Q_UPDATE_VERSION
+ Q_OVERWRITE_VERSION = internal.Q_OVERWRITE_VERSION
+ Q_ENFORCE_TRANSPORT = internal.Q_ENFORCE_TRANSPORT
+ Q_TRANSFER_VERSION = internal.Q_TRANSFER_VERSION
+ Q_TRANSFER_RESOURCE = internal.Q_TRANSFER_RESOURCE
+ Q_TRANSFER_SOURCE = internal.Q_TRANSFER_SOURCE
+)
+
+type (
+ SourceComponentVersion = internal.SourceComponentVersion
+ TargetRepositorySpec = internal.TargetRepositorySpec
+ TransferOptions = internal.TransferOptions
+
+ Artifact = internal.Artifact
+ AccessInfo = internal.UniformAccessSpecInfo
+ QuestionArguments = internal.QuestionArguments
+ ComponentVersionQuestionArguments = internal.ComponentVersionQuestionArguments
+ ComponentReferenceQuestionArguments = internal.ComponentReferenceQuestionArguments
+ ArtifactQuestionArguments = internal.ArtifactQuestionArguments
+ Resolution = internal.Resolution
+ DecisionRequestResult = internal.DecisionRequestResult
)
diff --git a/api/ocm/plugin/internal/access.go b/api/ocm/plugin/internal/access.go
index a4238a6533..6bae43934d 100644
--- a/api/ocm/plugin/internal/access.go
+++ b/api/ocm/plugin/internal/access.go
@@ -9,4 +9,6 @@ type AccessSpecInfo struct {
MediaType string `json:"mediaType"`
Hint string `json:"hint"`
ConsumerId credentials.ConsumerIdentity `json:"consumerId"`
+
+ Info *UniformAccessSpecInfo `json:"info,omitempty"`
}
diff --git a/api/ocm/plugin/internal/input.go b/api/ocm/plugin/internal/input.go
new file mode 100644
index 0000000000..a2b3b0cbbb
--- /dev/null
+++ b/api/ocm/plugin/internal/input.go
@@ -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"`
+}
diff --git a/api/ocm/plugin/internal/signing.go b/api/ocm/plugin/internal/signing.go
new file mode 100644
index 0000000000..495f987132
--- /dev/null
+++ b/api/ocm/plugin/internal/signing.go
@@ -0,0 +1,141 @@
+package internal
+
+import (
+ "crypto"
+ "crypto/x509/pkix"
+ "encoding/hex"
+ "encoding/json"
+ "encoding/pem"
+ "fmt"
+
+ "github.com/mandelsoft/goutils/errors"
+ "github.com/mandelsoft/goutils/generics"
+
+ "ocm.software/ocm/api/credentials"
+ v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1"
+ "ocm.software/ocm/api/tech/signing"
+ "ocm.software/ocm/api/tech/signing/signutils"
+)
+
+type (
+ SignatureSpec = v1.SignatureSpec
+)
+
+func SignatureSpecFor(sig *signing.Signature) *SignatureSpec {
+ return v1.SignatureSpecFor(sig)
+}
+
+type SigningContext struct {
+ HashAlgo crypto.Hash
+ PrivateKey signutils.GenericPrivateKey
+ PublicKey signutils.GenericPublicKey
+ Issuer *pkix.Name
+
+ Credentials credentials.DirectCredentials
+}
+
+var (
+ _ json.Unmarshaler = (*SigningContext)(nil)
+ _ json.Marshaler = (*SigningContext)(nil)
+)
+
+type signingContext struct {
+ HashAlgo string `json:"hashAlgorithm"`
+ PrivateKey string `json:"privatekey,omitempty"`
+ PublicKey string `json:"publicKey,omitempty"`
+ Issuer string `json:"issuer,omitempty"`
+ Credentials credentials.DirectCredentials `json:"credentials,omitempty"`
+}
+
+func (s SigningContext) MarshalJSON() ([]byte, error) {
+ var err error
+
+ ser := &signingContext{
+ HashAlgo: s.HashAlgo.String(),
+ }
+
+ if s.PrivateKey != nil {
+ ser.PrivateKey, err = encode(s.PrivateKey, signutils.GetPrivateKey, signutils.PemBlockForPrivateKey)
+ if err != nil {
+ return nil, err
+ }
+ }
+ if s.PublicKey != nil {
+ ser.PublicKey, err = encode(s.PublicKey, signutils.GetPublicKey, func(in interface{}) *pem.Block { return signutils.PemBlockForPublicKey(in) })
+ if err != nil {
+ return nil, err
+ }
+ }
+ if s.Issuer != nil {
+ ser.Issuer = signutils.DNAsString(*s.Issuer)
+ }
+ if s.Credentials != nil {
+ ser.Credentials = s.Credentials
+ }
+ return json.Marshal(ser)
+}
+
+func (s *SigningContext) UnmarshalJSON(bytes []byte) error {
+ var ser signingContext
+
+ err := json.Unmarshal(bytes, &ser)
+ if err != nil {
+ return err
+ }
+
+ h := signing.DefaultRegistry().GetHasher(ser.HashAlgo)
+ if h == nil {
+ return errors.ErrUnknown(signutils.KIND_HASH_ALGORITHM, ser.HashAlgo)
+ }
+ s.HashAlgo = h.Crypto()
+ if ser.Issuer != "" {
+ s.Issuer, err = signutils.ParseDN(ser.Issuer)
+ if err != nil {
+ return err
+ }
+ }
+
+ s.PrivateKey, err = decode[signutils.GenericPrivateKey](ser.PrivateKey, signutils.ParsePrivateKey)
+ if err != nil {
+ return err
+ }
+ s.PublicKey, err = decode[signutils.GenericPublicKey](ser.PublicKey, signutils.ParsePublicKey)
+ if err != nil {
+ return err
+ }
+ s.Credentials = ser.Credentials
+ return nil
+}
+
+func encode[T any](in T, f func(in T) (interface{}, error), e func(in interface{}) *pem.Block) (string, error) {
+ k, err := f(in)
+ if err != nil {
+ var i interface{} = in
+ switch d := i.(type) {
+ case []byte:
+ return hex.EncodeToString(d), nil
+ case string:
+ return hex.EncodeToString([]byte(d)), nil
+ default:
+ return "", fmt.Errorf("invalid key type")
+ }
+ }
+ b := e(k)
+ if b == nil {
+ return "", fmt.Errorf("cannot encode key")
+ }
+ return string(pem.EncodeToMemory(b)), nil
+}
+
+func decode[T any](in string, f func([]byte) (interface{}, error)) (T, error) {
+ var _nil T
+ k, err := f([]byte(in))
+ if err != nil {
+ data, err := hex.DecodeString(in)
+ if err != nil {
+ return _nil, err
+ }
+ k = data
+ }
+ return generics.Cast[T](k), nil
+}
diff --git a/api/ocm/plugin/internal/transfer.go b/api/ocm/plugin/internal/transfer.go
new file mode 100644
index 0000000000..29be906de0
--- /dev/null
+++ b/api/ocm/plugin/internal/transfer.go
@@ -0,0 +1,148 @@
+package internal
+
+import (
+ "encoding/json"
+ "reflect"
+
+ "github.com/mandelsoft/goutils/generics"
+
+ metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1"
+ v2 "ocm.software/ocm/api/ocm/compdesc/versions/v2"
+ ocm "ocm.software/ocm/api/ocm/cpi"
+ "ocm.software/ocm/api/tech"
+)
+
+const (
+ Q_UPDATE_VERSION = "updateversion"
+ Q_OVERWRITE_VERSION = "overwriteversion"
+ Q_ENFORCE_TRANSPORT = "enforcetransport"
+ Q_TRANSFER_VERSION = "transferversion"
+ Q_TRANSFER_RESOURCE = "transferresource"
+ Q_TRANSFER_SOURCE = "transfersource"
+)
+
+var TransferHandlerQuestions = map[string]reflect.Type{
+ Q_UPDATE_VERSION: generics.TypeOf[ComponentVersionQuestionArguments](),
+ Q_ENFORCE_TRANSPORT: generics.TypeOf[ComponentVersionQuestionArguments](),
+ Q_OVERWRITE_VERSION: generics.TypeOf[ComponentVersionQuestionArguments](),
+ Q_TRANSFER_VERSION: generics.TypeOf[ComponentReferenceQuestionArguments](),
+ Q_TRANSFER_RESOURCE: generics.TypeOf[ArtifactQuestionArguments](),
+ Q_TRANSFER_SOURCE: generics.TypeOf[ArtifactQuestionArguments](),
+}
+
+type UniformAccessSpecInfo = tech.UniformAccessSpecInfo
+
+type SourceComponentVersion struct {
+ Name string `json:"component"`
+ Version string `json:"version"`
+ Provider metav1.Provider `json:"provider,omitempty"`
+ Repository ocm.GenericRepositorySpec `json:"repository"`
+ Labels metav1.Labels `json:"labels,omitempty"`
+}
+
+type TargetRepositorySpec = ocm.GenericRepositorySpec
+
+// TransferOptions are the standard transfer options from
+// the standard transfer handler.
+// Like other transfer handler types it is possible to define
+// more options. Those non-standard options are passed
+// via a json.RawMessage in the field special.
+type TransferOptions struct {
+ Recursive *bool `json:"recursive,omitempty"`
+ ResourcesByValue *bool `json:"resourcesByValue,omitempty"`
+ LocalByValue *bool `json:"localByValue,omitempty"`
+ SourcesByValue *bool `json:"sourcesByValue,omitempty"`
+ KeepGlobalAccess *bool `json:"keepGlobalAccess,omitempty"`
+ StopOnExisting *bool `json:"stopOnExisting,omitempty"`
+ EnforceTransport *bool `json:"enforceTransport,omitempty"`
+ Overwrite *bool `json:"overwrite,omitempty"`
+ SkipUpdate *bool `json:"skipUpdate,omitempty"`
+ OmitAccessTypes []string `json:"omitAccessTypes,omitempty"`
+ OmitArtifactTypes []string `json:"omitArtifactTypes,omitempty"`
+ Special *json.RawMessage `json:"special,omitempty"`
+}
+
+// Resolution describes the transport context for a component
+// version, including the new handler specification and
+// the source repository to use to look up the reference.
+type Resolution struct {
+ RepositorySpec *ocm.GenericRepositorySpec `json:"repository,omitempty"`
+ // TransferHandler is the handler identity according to the transfer handler
+ // name scheme.
+ TransferHandler string `json:"transferHandler,omitempty"`
+ // TransferOptions may describe modified options used for sub-sequent
+ // transfers.
+ TransferOptions *TransferOptions `json:"transferOptions,omitempty"`
+}
+
+// DecisionRequestResult is the structure of the answer
+// the plugin has to return for a question.
+type DecisionRequestResult struct {
+ Error string `json:"error,omitempty"`
+ Decision bool `json:"decision"`
+ Resolution *Resolution `json:"resolution,omitempty"`
+}
+
+// QuestionArguments is the interface for the question attributes
+// differing for the various questions types.
+// There are three basic attribute sets:
+// - ComponentVersionQuestionArguments
+// - ComponentReferenceQuestionArguments
+// - ArtifactQuestionArguments
+//
+// For type assignments see TransferHandlerQuestions.
+type QuestionArguments interface {
+ QuestionArgumentsType() string
+}
+
+// ComponentVersionQuestionArguments describes the question arguments
+// given for a component version related question.
+type ComponentVersionQuestionArguments struct {
+ Source SourceComponentVersion `json:"source"`
+ Target TargetRepositorySpec `json:"target"`
+ Options TransferOptions `json:"options"`
+}
+
+var _ QuestionArguments = (*ComponentVersionQuestionArguments)(nil)
+
+func (a *ComponentVersionQuestionArguments) QuestionArgumentsType() string {
+ return "ComponentVersionQuestionArguments"
+}
+
+// ComponentReferenceQuestionArguments describes the question arguments
+// given for a component version reference related question.
+type ComponentReferenceQuestionArguments struct {
+ Source SourceComponentVersion `json:"source"`
+ Target TargetRepositorySpec `json:"target"`
+
+ v2.ElementMeta `json:",inline"`
+ ComponentName string `json:"componentName"`
+
+ Options TransferOptions `json:"options"`
+}
+
+var _ QuestionArguments = (*ComponentReferenceQuestionArguments)(nil)
+
+func (a *ComponentReferenceQuestionArguments) QuestionArgumentsType() string {
+ return "ComponentReferenceQuestionArguments"
+}
+
+type Artifact struct {
+ Meta v2.ElementMeta `json:"metadata"`
+ Access ocm.GenericAccessSpec `json:"access"`
+ AccessInfo UniformAccessSpecInfo `json:"accessInfo"`
+}
+
+// ArtifactQuestionArguments describes the question arguments
+// given for an artifact related question.
+type ArtifactQuestionArguments struct {
+ Source SourceComponentVersion `json:"source"`
+ Artifact Artifact `json:"artifact"`
+ Options TransferOptions `json:"options"`
+}
+
+var _ QuestionArguments = (*ArtifactQuestionArguments)(nil)
+
+func (a *ArtifactQuestionArguments) QuestionArgumentsType() string {
+ return "ArtifactQuestionArguments"
+}
diff --git a/api/ocm/plugin/plugin.go b/api/ocm/plugin/plugin.go
index f546a0781a..c4a171ebba 100644
--- a/api/ocm/plugin/plugin.go
+++ b/api/ocm/plugin/plugin.go
@@ -18,6 +18,7 @@ import (
"ocm.software/ocm/api/datacontext/attrs/clicfgattr"
"ocm.software/ocm/api/ocm"
"ocm.software/ocm/api/ocm/plugin/cache"
+ "ocm.software/ocm/api/ocm/plugin/descriptor"
"ocm.software/ocm/api/ocm/plugin/ppi"
"ocm.software/ocm/api/ocm/plugin/ppi/cmds/accessmethod"
"ocm.software/ocm/api/ocm/plugin/ppi/cmds/accessmethod/compose"
@@ -29,6 +30,11 @@ import (
"ocm.software/ocm/api/ocm/plugin/ppi/cmds/download"
"ocm.software/ocm/api/ocm/plugin/ppi/cmds/mergehandler"
merge "ocm.software/ocm/api/ocm/plugin/ppi/cmds/mergehandler/execute"
+ signingcmd "ocm.software/ocm/api/ocm/plugin/ppi/cmds/signing"
+ "ocm.software/ocm/api/ocm/plugin/ppi/cmds/signing/consumer"
+ "ocm.software/ocm/api/ocm/plugin/ppi/cmds/signing/sign"
+ "ocm.software/ocm/api/ocm/plugin/ppi/cmds/signing/verify"
+ "ocm.software/ocm/api/ocm/plugin/ppi/cmds/transferhandler"
"ocm.software/ocm/api/ocm/plugin/ppi/cmds/upload"
"ocm.software/ocm/api/ocm/plugin/ppi/cmds/upload/put"
uplval "ocm.software/ocm/api/ocm/plugin/ppi/cmds/upload/validate"
@@ -36,6 +42,7 @@ import (
vscompose "ocm.software/ocm/api/ocm/plugin/ppi/cmds/valueset/compose"
vsval "ocm.software/ocm/api/ocm/plugin/ppi/cmds/valueset/validate"
"ocm.software/ocm/api/ocm/valuemergehandler"
+ "ocm.software/ocm/api/tech/signing"
"ocm.software/ocm/api/utils/cobrautils/flagsets"
"ocm.software/ocm/api/utils/cobrautils/logopts/logging"
"ocm.software/ocm/api/utils/runtime"
@@ -443,3 +450,108 @@ func (p *pluginImpl) Command(name string, reader io.Reader, writer io.Writer, cm
_, err := p.Exec(reader, writer, args...)
return err
}
+
+func (p *pluginImpl) AskTransferQuestion(name string, question string, args interface{}) (*DecisionRequestResult, error) {
+ argdata, err := json.Marshal(args)
+ if err != nil {
+ return nil, errors.Wrapf(err, "cannot marshal option values")
+ }
+
+ result, err := p.Exec(bytes.NewReader(argdata), nil, transferhandler.Name, name, question)
+ if err != nil {
+ return nil, err
+ }
+ var r DecisionRequestResult
+ err = json.Unmarshal(result, &r)
+ if err != nil {
+ return nil, errors.Wrapf(err, "cannot unmarshal composition result")
+ }
+
+ if r.Error == "" {
+ return &r, nil
+ }
+ return &r, errors.New(r.Error)
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+func (p *pluginImpl) GetSigningConsumer(name string, sctx signing.SigningContext) (credentials.ConsumerIdentity, error) {
+ sh := p.GetDescriptor().SigningHandlers.Get(name)
+ if sh == nil {
+ return nil, errors.ErrUnknown(descriptor.KIND_SIGNING_HANDLER, name)
+ }
+ if !sh.Credentials {
+ return nil, nil
+ }
+
+ ser := ppi.SigningContext{
+ HashAlgo: sctx.GetHash(),
+ PrivateKey: sctx.GetPrivateKey(),
+ PublicKey: sctx.GetPublicKey(),
+ Issuer: sctx.GetIssuer(),
+ }
+ indata, err := json.Marshal(ser)
+ if err != nil {
+ return nil, errors.Wrapf(err, "cannot marshal signing context")
+ }
+
+ result, err := p.Exec(bytes.NewReader(indata), nil, signingcmd.Name, consumer.Name, name)
+ if err != nil {
+ return nil, err
+ }
+ var r credentials.ConsumerIdentity
+ err = json.Unmarshal(result, &r)
+ if err != nil {
+ return nil, errors.Wrapf(err, "cannot unmarshal signing result")
+ }
+
+ return r, nil
+}
+
+func (p *pluginImpl) Sign(name string, digest string, creds credentials.DirectCredentials, sctx signing.SigningContext) (*signing.Signature, error) {
+ ser := ppi.SigningContext{
+ HashAlgo: sctx.GetHash(),
+ PrivateKey: sctx.GetPrivateKey(),
+ PublicKey: sctx.GetPublicKey(),
+ Issuer: sctx.GetIssuer(),
+
+ Credentials: creds,
+ }
+ indata, err := json.Marshal(ser)
+ if err != nil {
+ return nil, errors.Wrapf(err, "cannot marshal signing context")
+ }
+
+ result, err := p.Exec(bytes.NewReader(indata), nil, signingcmd.Name, sign.Name, name, digest)
+ if err != nil {
+ return nil, err
+ }
+ var r SignatureSpec
+ err = json.Unmarshal(result, &r)
+ if err != nil {
+ return nil, errors.Wrapf(err, "cannot unmarshal signing result")
+ }
+
+ return r.ConvertToSigning(), nil
+}
+
+func (p *pluginImpl) Verify(name string, digest string, signature *signing.Signature, sctx signing.SigningContext) error {
+ ser := ppi.SigningContext{
+ HashAlgo: sctx.GetHash(),
+ PrivateKey: sctx.GetPrivateKey(),
+ PublicKey: sctx.GetPublicKey(),
+ Issuer: sctx.GetIssuer(),
+ }
+ indata, err := json.Marshal(ser)
+ if err != nil {
+ return errors.Wrapf(err, "cannot marshal signing context")
+ }
+
+ sig := SignatureSpecFor(signature)
+ sigdata, err := json.Marshal(sig)
+ if err != nil {
+ return errors.Wrapf(err, "cannot marshal signature")
+ }
+ _, err = p.Exec(bytes.NewReader(indata), nil, signingcmd.Name, verify.Name, name, digest, string(sigdata))
+ return err
+}
diff --git a/api/ocm/plugin/plugin_input.go b/api/ocm/plugin/plugin_input.go
new file mode 100644
index 0000000000..4b04df5a27
--- /dev/null
+++ b/api/ocm/plugin/plugin_input.go
@@ -0,0 +1,70 @@
+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/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, dir string, creds, spec json.RawMessage) error {
+ args := []string{input.Name, get.Name, dir, string(spec)}
+ if creds != nil {
+ args = append(args, "--"+get.OptCreds, string(creds))
+ }
+ _, err := p.Exec(nil, w, args...)
+ return err
+}
diff --git a/api/ocm/plugin/plugin_test.go b/api/ocm/plugin/plugin_test.go
index 870e2bdcd9..3968008f34 100644
--- a/api/ocm/plugin/plugin_test.go
+++ b/api/ocm/plugin/plugin_test.go
@@ -82,7 +82,7 @@ someattr: value
Expect(err).To(Succeed())
spec := s.(*access.AccessSpec)
h := spec.Handler()
- info, err := h.Info(spec)
+ info, err := h.GetAccessSpecInfo(spec)
Expect(err).To(Succeed())
Expect(info).To(Equal(&plugin.AccessSpecInfo{
Short: "a test",
diff --git a/api/ocm/plugin/ppi/cmds/app.go b/api/ocm/plugin/ppi/cmds/app.go
index b2129bfef4..1f700b00b6 100644
--- a/api/ocm/plugin/ppi/cmds/app.go
+++ b/api/ocm/plugin/ppi/cmds/app.go
@@ -4,6 +4,7 @@ package cmds
import (
"encoding/json"
+ "io"
"os"
"github.com/spf13/cobra"
@@ -15,8 +16,11 @@ 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"
+ "ocm.software/ocm/api/ocm/plugin/ppi/cmds/transferhandler"
"ocm.software/ocm/api/ocm/plugin/ppi/cmds/upload"
"ocm.software/ocm/api/ocm/plugin/ppi/cmds/valueset"
"ocm.software/ocm/api/utils/cobrautils"
@@ -31,7 +35,7 @@ func (p *PluginCommand) Command() *cobra.Command {
return p.command
}
-func NewPluginCommand(p ppi.Plugin) *PluginCommand {
+func NewPluginCommand(p ppi.Plugin, opts ...Option) *PluginCommand {
short := p.Descriptor().Short
if short == "" {
short = "OCM plugin " + p.Name()
@@ -62,10 +66,13 @@ func NewPluginCommand(p ppi.Plugin) *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))
cmd.AddCommand(command.New(p))
+ cmd.AddCommand(transferhandler.New(p))
+ cmd.AddCommand(signing.New(p))
cmd.InitDefaultHelpCmd()
help := cobrautils.GetHelpCommand(cmd)
@@ -76,8 +83,12 @@ func NewPluginCommand(p ppi.Plugin) *PluginCommand {
help.AddCommand(descriptor.New())
- p.GetOptions().AddFlags(cmd.Flags())
pcmd.command = cmd
+ for _, o := range opts {
+ o.ApplyTo(pcmd)
+ }
+
+ p.GetOptions().AddFlags(cmd.Flags())
return pcmd
}
@@ -92,6 +103,18 @@ func (p *PluginCommand) PreRunE(cmd *cobra.Command, args []string) error {
return nil
}
+func (p *PluginCommand) SetIO(in io.Reader, out, err io.Writer) {
+ if in != nil {
+ p.command.SetIn(in)
+ }
+ if out != nil {
+ p.command.SetOut(out)
+ }
+ if err != nil {
+ p.command.SetErr(err)
+ }
+}
+
func (p *PluginCommand) Execute(args []string) error {
p.command.SetArgs(args)
err := p.command.Execute()
diff --git a/api/ocm/plugin/ppi/cmds/descopts.go b/api/ocm/plugin/ppi/cmds/descopts.go
new file mode 100644
index 0000000000..29b8f56ac8
--- /dev/null
+++ b/api/ocm/plugin/ppi/cmds/descopts.go
@@ -0,0 +1,49 @@
+package cmds
+
+import (
+ "io"
+
+ "github.com/mandelsoft/goutils/optionutils"
+)
+
+type Option = optionutils.Option[*PluginCommand]
+
+type stdout struct {
+ io.Writer
+}
+
+func StdOut(w io.Writer) Option {
+ return &stdout{w}
+}
+
+func (o *stdout) ApplyTo(p *PluginCommand) {
+ p.command.SetOut(o.Writer)
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+type stderr struct {
+ io.Writer
+}
+
+func StdErr(w io.Writer) Option {
+ return &stderr{w}
+}
+
+func (o *stderr) ApplyTo(p *PluginCommand) {
+ p.command.SetErr(o.Writer)
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+type stdin struct {
+ io.Reader
+}
+
+func StdIn(r io.Reader) Option {
+ return &stdin{r}
+}
+
+func (o *stdin) ApplyTo(p *PluginCommand) {
+ p.command.SetIn(o.Reader)
+}
diff --git a/api/ocm/plugin/ppi/cmds/input/cmd.go b/api/ocm/plugin/ppi/cmds/input/cmd.go
new file mode 100644
index 0000000000..444d84f508
--- /dev/null
+++ b/api/ocm/plugin/ppi/cmds/input/cmd.go
@@ -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 (` + p.Name() + ` descriptor.`,
+ }
+
+ cmd.AddCommand(validate.New(p))
+ cmd.AddCommand(get.New(p))
+ cmd.AddCommand(compose.New(p))
+ return cmd
+}
diff --git a/api/ocm/plugin/ppi/cmds/input/compose/cmd.go b/api/ocm/plugin/ppi/cmds/input/compose/cmd.go
new file mode 100644
index 0000000000..5add87613f
--- /dev/null
+++ b/api/ocm/plugin/ppi/cmds/input/compose/cmd.go
@@ -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/plugin/ppi"
+ "ocm.software/ocm/api/utils/runtime"
+ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options"
+)
+
+const Name = "compose"
+
+func New(p ppi.Plugin) *cobra.Command {
+ opts := Options{}
+
+ cmd := &cobra.Command{
+ Use: Name + " ",
+ 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 (` + p.Name() + ` descriptor).
+
+If possible, predefined standard options should be used. In such a case only the
+name field should be defined for an option. If required, new options can be
+defined by additionally specifying a type and a description. New options should
+be used very carefully. The chosen names MUST not conflict with names provided
+by other plugins. Therefore, it is highly recommended to use names prefixed
+by the plugin name.
+
+` + options.DefaultRegistry.Usage(),
+ Args: cobra.ExactArgs(3),
+ PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
+ return opts.Complete(args)
+ },
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return Command(p, cmd, &opts)
+ },
+ }
+ opts.AddFlags(cmd.Flags())
+ return cmd
+}
+
+type Options struct {
+ Name string
+ Options ppi.Config
+ Base ppi.Config
+}
+
+func (o *Options) AddFlags(fs *pflag.FlagSet) {
+}
+
+func (o *Options) Complete(args []string) error {
+ o.Name = args[0]
+ if err := runtime.DefaultYAMLEncoding.Unmarshal([]byte(args[1]), &o.Options); err != nil {
+ return errors.Wrapf(err, "invalid 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
+}
diff --git a/api/ocm/plugin/ppi/cmds/input/get/cmd.go b/api/ocm/plugin/ppi/cmds/input/get/cmd.go
new file mode 100644
index 0000000000..d310a32786
--- /dev/null
+++ b/api/ocm/plugin/ppi/cmds/input/get/cmd.go
@@ -0,0 +1,95 @@
+package get
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "os"
+
+ "github.com/mandelsoft/goutils/errors"
+ "github.com/spf13/cobra"
+ "github.com/spf13/pflag"
+
+ "ocm.software/ocm/api/credentials"
+ "ocm.software/ocm/api/ocm/plugin/descriptor"
+ "ocm.software/ocm/api/ocm/plugin/ppi"
+ commonppi "ocm.software/ocm/api/ocm/plugin/ppi/cmds/common"
+ "ocm.software/ocm/api/utils/cobrautils/flag"
+ "ocm.software/ocm/api/utils/runtime"
+)
+
+const (
+ Name = "get"
+ OptCreds = commonppi.OptCreds
+)
+
+func New(p ppi.Plugin) *cobra.Command {
+ opts := Options{}
+
+ cmd := &cobra.Command{
+ Use: Name + " [] [] ",
+ Short: "get blob",
+ Long: `
+Evaluate the given input specification and return the described blob on
+*stdout*.`,
+ Args: cobra.RangeArgs(1, 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 {
+ Credentials credentials.DirectCredentials
+ Specification json.RawMessage
+ Dir string
+}
+
+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 {
+ creds := 0
+ if len(args) > 1 {
+ o.Dir = args[creds]
+ creds++
+ } else {
+ o.Dir = "."
+ }
+ if err := runtime.DefaultYAMLEncoding.Unmarshal([]byte(args[creds]), &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, opts.Dir, spec)
+ if err != nil {
+ return err
+ }
+ r, err := m.Reader(p, opts.Dir, spec, opts.Credentials)
+ if err != nil {
+ return err
+ }
+ _, err = io.Copy(os.Stdout, r)
+ r.Close()
+ return err
+}
diff --git a/api/ocm/plugin/ppi/cmds/input/validate/cmd.go b/api/ocm/plugin/ppi/cmds/input/validate/cmd.go
new file mode 100644
index 0000000000..60a08c787d
--- /dev/null
+++ b/api/ocm/plugin/ppi/cmds/input/validate/cmd.go
@@ -0,0 +1,113 @@
+package validate
+
+import (
+ "encoding/json"
+
+ "github.com/mandelsoft/goutils/errors"
+ "github.com/spf13/cobra"
+ "github.com/spf13/pflag"
+
+ "ocm.software/ocm/api/credentials"
+ "ocm.software/ocm/api/ocm/plugin/descriptor"
+ "ocm.software/ocm/api/ocm/plugin/ppi"
+ "ocm.software/ocm/api/utils/runtime"
+)
+
+const Name = "validate"
+
+func New(p ppi.Plugin) *cobra.Command {
+ opts := Options{}
+
+ cmd := &cobra.Command{
+ Use: Name + " ",
+ Short: "validate input specification",
+ Long: `
+This command accepts an input specification as argument. It is used to
+validate the specification and to provide some metadata for the given
+specification.
+
+This metadata has to be provided as JSON string on *stdout* and has the
+following fields:
+
+- **mediaType** *string*
+
+ The media type of the artifact described by the specification. It may be part
+ of the specification or implicitly determined by the input type.
+
+- **description** *string*
+
+ A short textual description of the described location.
+
+- **hint** *string*
+
+ A name hint of the described location used to reconstruct a useful
+ name for local blobs uploaded to a dedicated repository technology.
+
+- **consumerId** *map[string]string*
+
+ The consumer id used to determine optional credentials for the
+ underlying repository. If specified, at least the type field must be set.
+`,
+ Args: cobra.RangeArgs(1, 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 {
+ Specification json.RawMessage
+ Dir string
+}
+
+func (o *Options) AddFlags(fs *pflag.FlagSet) {
+}
+
+func (o *Options) Complete(args []string) error {
+ creds := 0
+ if len(args) > 1 {
+ o.Dir = args[creds]
+ creds++
+ } else {
+ o.Dir = "."
+ }
+ if err := runtime.DefaultYAMLEncoding.Unmarshal([]byte(args[creds]), &o.Specification); err != nil {
+ return errors.Wrapf(err, "invalid access specification")
+ }
+ return nil
+}
+
+type Result struct {
+ MediaType string `json:"mediaType"`
+ Short string `json:"description"`
+ Hint string `json:"hint"`
+ ConsumerId credentials.ConsumerIdentity `json:"consumerId"`
+}
+
+func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error {
+ spec, err := p.DecodeInputSpecification(opts.Specification)
+ if err != nil {
+ return errors.Wrapf(err, "input specification")
+ }
+
+ m := p.GetInputType(spec.GetType())
+ if m == nil {
+ return errors.ErrUnknown(descriptor.KIND_INPUTTYPE, spec.GetType())
+ }
+ info, err := m.ValidateSpecification(p, opts.Dir, spec)
+ if err != nil {
+ return err
+ }
+ result := Result{MediaType: info.MediaType, ConsumerId: info.ConsumerId, Hint: info.Hint, Short: info.Short}
+ data, err := json.Marshal(result)
+ if err != nil {
+ return err
+ }
+ cmd.Printf("%s\n", string(data))
+ return nil
+}
diff --git a/api/ocm/plugin/ppi/cmds/signing/cmd.go b/api/ocm/plugin/ppi/cmds/signing/cmd.go
new file mode 100644
index 0000000000..018abaea66
--- /dev/null
+++ b/api/ocm/plugin/ppi/cmds/signing/cmd.go
@@ -0,0 +1,26 @@
+package signing
+
+import (
+ "github.com/spf13/cobra"
+
+ "ocm.software/ocm/api/ocm/plugin/ppi"
+ "ocm.software/ocm/api/ocm/plugin/ppi/cmds/signing/consumer"
+ "ocm.software/ocm/api/ocm/plugin/ppi/cmds/signing/sign"
+ "ocm.software/ocm/api/ocm/plugin/ppi/cmds/signing/verify"
+)
+
+const Name = "signing"
+
+func New(p ppi.Plugin) *cobra.Command {
+ cmd := &cobra.Command{
+ Use: Name,
+ Short: "signing related operations",
+ Long: `This command group provides all commands used for the signing extension
+described by signing descriptor (` + p.Name() + ` descriptor).`,
+ }
+
+ cmd.AddCommand(consumer.New(p))
+ cmd.AddCommand(sign.New(p))
+ cmd.AddCommand(verify.New(p))
+ return cmd
+}
diff --git a/api/ocm/plugin/ppi/cmds/signing/consumer/cmd.go b/api/ocm/plugin/ppi/cmds/signing/consumer/cmd.go
new file mode 100644
index 0000000000..0b7666336f
--- /dev/null
+++ b/api/ocm/plugin/ppi/cmds/signing/consumer/cmd.go
@@ -0,0 +1,91 @@
+package consumer
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+
+ "github.com/mandelsoft/goutils/errors"
+ "github.com/spf13/cobra"
+ "github.com/spf13/pflag"
+
+ "ocm.software/ocm/api/ocm/plugin/descriptor"
+ "ocm.software/ocm/api/ocm/plugin/ppi"
+ "ocm.software/ocm/api/tech/signing"
+)
+
+const (
+ Name = "consumer"
+)
+
+func New(p ppi.Plugin) *cobra.Command {
+ opts := Options{}
+
+ cmd := &cobra.Command{
+ Use: Name + " ",
+ Short: "determine consumer id",
+ Long: `
+Provide the required credential consumer id for signing context taken from
+*stdin*. The id is returned by *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 {
+ Name string
+}
+
+func (o *Options) AddFlags(fs *pflag.FlagSet) {
+}
+
+func (o *Options) Complete(args []string) error {
+ o.Name = args[0]
+ return nil
+}
+
+func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error {
+ data, err := io.ReadAll(cmd.InOrStdin())
+ if err != nil {
+ return err
+ }
+
+ var ctx ppi.SigningContext
+ err = json.Unmarshal(data, &ctx)
+ if err != nil {
+ return err
+ }
+
+ h := p.GetSigningHandler(opts.Name)
+ if h == nil {
+ return errors.ErrUnknown(descriptor.KIND_SIGNING_HANDLER)
+ }
+ c := h.GetConsumerProvider()
+ if c == nil {
+ return fmt.Errorf("handler %q does not support consumer provider", opts.Name)
+ }
+
+ sctx := &signing.DefaultSigningContext{
+ Hash: ctx.HashAlgo,
+ PrivateKey: ctx.PrivateKey,
+ PublicKey: ctx.PublicKey,
+ Issuer: ctx.Issuer,
+ }
+
+ cid := c(sctx)
+
+ data, err = json.Marshal(cid)
+ if err != nil {
+ return err
+ }
+ cmd.OutOrStdout().Write(data)
+ return nil
+}
diff --git a/api/ocm/plugin/ppi/cmds/signing/sign/cmd.go b/api/ocm/plugin/ppi/cmds/signing/sign/cmd.go
new file mode 100644
index 0000000000..9c1afcdacd
--- /dev/null
+++ b/api/ocm/plugin/ppi/cmds/signing/sign/cmd.go
@@ -0,0 +1,103 @@
+package sign
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+
+ "github.com/mandelsoft/goutils/errors"
+ "github.com/spf13/cobra"
+ "github.com/spf13/pflag"
+
+ "ocm.software/ocm/api/credentials"
+ "ocm.software/ocm/api/datacontext"
+ "ocm.software/ocm/api/ocm/plugin/descriptor"
+ "ocm.software/ocm/api/ocm/plugin/ppi"
+ "ocm.software/ocm/api/tech/signing"
+)
+
+const (
+ Name = "sign"
+)
+
+func New(p ppi.Plugin) *cobra.Command {
+ opts := Options{}
+
+ cmd := &cobra.Command{
+ Use: Name + " ",
+ Short: "sign digest",
+ Long: `
+Sign the given digest with signing context taken from *stdin* and return the signature on *stdout*.`,
+ 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
+ Digest string
+}
+
+func (o *Options) AddFlags(fs *pflag.FlagSet) {
+}
+
+func (o *Options) Complete(args []string) error {
+ o.Name = args[0]
+ o.Digest = args[1]
+ return nil
+}
+
+func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error {
+ data, err := io.ReadAll(cmd.InOrStdin())
+ if err != nil {
+ return err
+ }
+
+ var ctx ppi.SigningContext
+ err = json.Unmarshal(data, &ctx)
+ if err != nil {
+ return err
+ }
+
+ h := p.GetSigningHandler(opts.Name)
+ if h == nil {
+ return errors.ErrUnknown(descriptor.KIND_SIGNING_HANDLER)
+ }
+ s := h.GetSigner()
+ if s == nil {
+ return fmt.Errorf("handler %q does not support signing", opts.Name)
+ }
+
+ sctx := &signing.DefaultSigningContext{
+ Hash: ctx.HashAlgo,
+ PrivateKey: ctx.PrivateKey,
+ PublicKey: ctx.PublicKey,
+ Issuer: ctx.Issuer,
+ }
+
+ cctx := credentials.New(datacontext.MODE_EXTENDED)
+ if h.GetConsumerProvider() != nil && ctx.Credentials != nil {
+ cid := h.GetConsumerProvider()(sctx)
+ cctx.SetCredentialsForConsumer(cid, ctx.Credentials)
+ }
+
+ sig, err := s.Sign(cctx, opts.Digest, sctx)
+ if err != nil {
+ return err
+ }
+
+ spec := ppi.SignatureSpecFor(sig)
+ out, err := json.Marshal(spec)
+ if err != nil {
+ return err
+ }
+ _, err = cmd.OutOrStdout().Write(out)
+ return err
+}
diff --git a/api/ocm/plugin/ppi/cmds/signing/verify/cmd.go b/api/ocm/plugin/ppi/cmds/signing/verify/cmd.go
new file mode 100644
index 0000000000..0d5761df51
--- /dev/null
+++ b/api/ocm/plugin/ppi/cmds/signing/verify/cmd.go
@@ -0,0 +1,93 @@
+package verify
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+
+ "github.com/mandelsoft/goutils/errors"
+ "github.com/spf13/cobra"
+ "github.com/spf13/pflag"
+
+ "ocm.software/ocm/api/ocm/plugin/descriptor"
+ "ocm.software/ocm/api/ocm/plugin/ppi"
+ "ocm.software/ocm/api/tech/signing"
+)
+
+const (
+ Name = "verify"
+)
+
+func New(p ppi.Plugin) *cobra.Command {
+ opts := Options{}
+
+ cmd := &cobra.Command{
+ Use: Name + " ",
+ Short: "verify signature",
+ Long: `
+Verify the given digest and signature with signing context taken from *stdin*.`,
+ 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
+ Digest string
+ Signature *signing.Signature
+}
+
+func (o *Options) AddFlags(fs *pflag.FlagSet) {
+}
+
+func (o *Options) Complete(args []string) error {
+ var sig ppi.SignatureSpec
+
+ err := json.Unmarshal([]byte(args[2]), &sig)
+ if err != nil {
+ return err
+ }
+
+ o.Name = args[0]
+ o.Digest = args[1]
+ o.Signature = sig.ConvertToSigning()
+ return nil
+}
+
+func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error {
+ data, err := io.ReadAll(cmd.InOrStdin())
+ if err != nil {
+ return err
+ }
+
+ var ctx ppi.SigningContext
+ err = json.Unmarshal(data, &ctx)
+ if err != nil {
+ return err
+ }
+
+ h := p.GetSigningHandler(opts.Name)
+ if h == nil {
+ return errors.ErrUnknown(descriptor.KIND_SIGNING_HANDLER)
+ }
+ v := h.GetVerifier()
+ if v == nil {
+ return fmt.Errorf("handler %q does not support verifying", opts.Name)
+ }
+
+ sctx := &signing.DefaultSigningContext{
+ Hash: ctx.HashAlgo,
+ PrivateKey: ctx.PrivateKey,
+ PublicKey: ctx.PublicKey,
+ Issuer: ctx.Issuer,
+ }
+
+ return v.Verify(opts.Digest, opts.Signature, sctx)
+}
diff --git a/api/ocm/plugin/ppi/cmds/transferhandler/cmd.go b/api/ocm/plugin/ppi/cmds/transferhandler/cmd.go
new file mode 100644
index 0000000000..ef00ee996c
--- /dev/null
+++ b/api/ocm/plugin/ppi/cmds/transferhandler/cmd.go
@@ -0,0 +1,117 @@
+package transferhandler
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "reflect"
+
+ "github.com/spf13/cobra"
+ "github.com/spf13/pflag"
+
+ "ocm.software/ocm/api/ocm/plugin/ppi"
+)
+
+const Name = "transferhandler"
+
+func New(p ppi.Plugin) *cobra.Command {
+ opts := Options{}
+ cmd := &cobra.Command{
+ Use: Name + " ",
+ Short: "decide on a question related to a component version transport",
+ Long: `
+The task of this command is to decide on questions related to the transport
+of component versions, their resources and sources.
+
+This command is only used for actions for which the transfer handler descriptor
+enables the particular question.
+
+The question arguments are passed as JSON structure on *stdin*,
+the result has to be returned as JSON document
+on *stdout*.
+
+There are several questions a handler can answer:
+- transferversion: This action answers the question, whether
+ a component version shall be transported at all and how it should be
+ transported. The argument is a ComponentVersionQuestion
+ and the result decision is extended by an optional transport context
+ consisting, of a new transport handler description and transport options.
+- enforcetransport: This action answers the question, whether
+ a component version shall be transported as if it is not yet present
+ in the target repository. The argument is a ComponentVersionQuestion.
+- updateversion: Update non-signature relevant information.
+ The argument is a ComponentVersionQuestion.
+- overwriteversion: Override signature-relevant information.
+ The argument is a ComponentVersionQuestion.
+- transferresource: Transport resource as value. The argument
+ is an ArtifactQuestion.
+- transfersource: Transport source as value. The argument
+ is an ArtifactQuestion.
+
+For detailed types see ocm.software/ocm/api/ocm/plugin/ppi/questions.go.
+`,
+ 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 {
+ // Handler is the handler name as provided by the plugin.
+ Handler string
+ // Question cis the name of the question
+ Question string
+}
+
+func (o *Options) AddFlags(fs *pflag.FlagSet) {
+}
+
+func (o *Options) Complete(args []string) error {
+ o.Handler = args[0]
+ o.Question = args[1]
+ return nil
+}
+
+func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error {
+ t := ppi.TransferHandlerQuestions[opts.Question]
+ if t == nil {
+ return fmt.Errorf("unknown question %q", opts.Question)
+ }
+ h := p.GetTransferHandler(opts.Handler)
+ if h == nil {
+ return fmt.Errorf("unknown transfer handler %q", opts.Handler)
+ }
+ for _, q := range h.GetQuestions() {
+ if q.GetQuestion() == opts.Question {
+ data, err := io.ReadAll(cmd.InOrStdin())
+ if err != nil {
+ return err
+ }
+
+ args := reflect.New(t).Interface().(ppi.QuestionArguments)
+ err = json.Unmarshal(data, args)
+ if err != nil {
+ return err
+ }
+
+ var result ppi.DecisionRequestResult
+ result.Decision, err = q.DecideOn(p, args)
+ if err != nil {
+ result.Error = err.Error()
+ }
+ data, err = json.Marshal(result)
+ if err != nil {
+ return err
+ }
+ cmd.Printf("%s\n", string(data))
+ return nil
+ }
+ }
+ return fmt.Errorf("question %q not configured for transfer handler %q", opts.Question, opts.Handler)
+}
diff --git a/api/ocm/plugin/ppi/cmds/valueset/compose/cmd.go b/api/ocm/plugin/ppi/cmds/valueset/compose/cmd.go
index 1e5ca775e1..75303d496e 100644
--- a/api/ocm/plugin/ppi/cmds/valueset/compose/cmd.go
+++ b/api/ocm/plugin/ppi/cmds/valueset/compose/cmd.go
@@ -29,7 +29,10 @@ The finally composed set has to be returned as JSON document
on *stdout*.
This command is only used, if for a value set descriptor configuration
-na direct composition rules are configured (` + p.Name() + ` descriptor).
+no direct composition rules are configured (` + p.Name() + ` descriptor).
+
+The purpose describes the purpose the values set is used for:
+- routingslip a value used for a routing slip entry.
If possible, predefined standard options should be used. In such a case only the
name field should be defined for an option. If required, new options can be
diff --git a/api/ocm/plugin/ppi/interface.go b/api/ocm/plugin/ppi/interface.go
index bb35c71b04..eba7fe0906 100644
--- a/api/ocm/plugin/ppi/interface.go
+++ b/api/ocm/plugin/ppi/interface.go
@@ -9,9 +9,10 @@ import (
"ocm.software/ocm/api/config/cpi"
"ocm.software/ocm/api/credentials"
"ocm.software/ocm/api/datacontext/action"
- "ocm.software/ocm/api/ocm/extensions/accessmethods/options"
"ocm.software/ocm/api/ocm/plugin/descriptor"
"ocm.software/ocm/api/ocm/plugin/internal"
+ "ocm.software/ocm/api/tech/signing"
+ "ocm.software/ocm/api/utils/cobrautils/flagsets"
"ocm.software/ocm/api/utils/runtime"
)
@@ -26,10 +27,21 @@ type (
ActionSpecInfo = internal.ActionSpecInfo
AccessSpecInfo = internal.AccessSpecInfo
+ InputSpecInfo = internal.InputSpecInfo
ValueSetInfo = internal.ValueSetInfo
UploadTargetSpecInfo = internal.UploadTargetSpecInfo
+
+ SourceComponentVersion = internal.SourceComponentVersion
+ TargetRepositorySpec = internal.TargetRepositorySpec
+ StandardTransferOptions = internal.TransferOptions
+
+ SignatureSpec = internal.SignatureSpec
)
+func SignatureSpecFor(sig *signing.Signature) *SignatureSpec {
+ return internal.SignatureSpecFor(sig)
+}
+
var REALM = descriptor.REALM
type Plugin interface {
@@ -57,6 +69,10 @@ type Plugin interface {
DecodeAccessSpecification(data []byte) (AccessSpec, error)
GetAccessMethod(name string, version string) AccessMethod
+ RegisterInputType(m InputType) error
+ DecodeInputSpecification(data []byte) (InputSpec, error)
+ GetInputType(name string) InputType
+
RegisterAction(a Action) error
DecodeAction(data []byte) (ActionSpec, error)
GetAction(name string) Action
@@ -72,6 +88,14 @@ type Plugin interface {
GetCommand(name string) Command
Commands() []Command
+ RegisterTransferHandler(h TransferHandler) error
+ GetTransferHandler(name string) TransferHandler
+ TransferHandlers() []TransferHandler
+
+ RegisterSigningHandler(h SigningHandler) error
+ GetSigningHandler(name string) SigningHandler
+ SigningHandlers() []SigningHandler
+
RegisterConfigType(c cpi.ConfigType) error
GetConfigType(name string) *descriptor.ConfigTypeDescriptor
ConfigTypes() []descriptor.ConfigTypeDescriptor
@@ -80,6 +104,31 @@ type Plugin interface {
GetConfig() (interface{}, error)
}
+////////////////////////////////////////////////////////////////////////////////
+
+type InputType interface {
+ runtime.TypedObjectDecoder[InputSpec]
+
+ Name() string
+
+ // Options provides the list of CLI options supported to compose the access
+ // specification.
+ Options() []flagsets.ConfigOptionType
+
+ // 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, dir string, spec InputSpec) (info *InputSpecInfo, err error)
+ Reader(p Plugin, dir string, spec InputSpec, creds credentials.Credentials) (io.ReadCloser, error)
+ ComposeSpecification(p Plugin, opts Config, config Config) error
+}
+
+type InputSpec = runtime.TypedObject
+
+////////////////////////////////////////////////////////////////////////////////
+
type AccessMethod interface {
runtime.TypedObjectDecoder[AccessSpec]
@@ -88,7 +137,7 @@ type AccessMethod interface {
// Options provides the list of CLI options supported to compose the access
// specification.
- Options() []options.OptionType
+ Options() []flagsets.ConfigOptionType
// Description provides a general description for the access mehod kind.
Description() string
@@ -104,6 +153,8 @@ type AccessSpec = runtime.TypedObject
type AccessSpecProvider func() AccessSpec
+////////////////////////////////////////////////////////////////////////////////
+
type UploadFormats runtime.KnownTypes[runtime.TypedObject, runtime.TypedObjectDecoder[runtime.TypedObject]]
type Uploader interface {
@@ -118,6 +169,8 @@ type Uploader interface {
type UploadTargetSpec = runtime.TypedObject
+////////////////////////////////////////////////////////////////////////////////
+
type DownloadResultProvider func() (string, error)
type Downloader interface {
@@ -128,6 +181,8 @@ type Downloader interface {
Writer(p Plugin, arttype, mediatype string, filepath string, config []byte) (io.WriteCloser, DownloadResultProvider, error)
}
+////////////////////////////////////////////////////////////////////////////////
+
type ActionSpec = action.ActionSpec
type ActionResult = action.ActionResult
@@ -141,6 +196,8 @@ type Action interface {
Execute(p Plugin, spec ActionSpec, creds credentials.DirectCredentials) (result ActionResult, err error)
}
+////////////////////////////////////////////////////////////////////////////////
+
type Value = runtime.RawValue
type ValueMergeResult struct {
@@ -173,7 +230,7 @@ type ValueSet interface {
// Options provides the list of CLI options supported to compose the access
// specification.
- Options() []options.OptionType
+ Options() []flagsets.ConfigOptionType
// Description provides a general description for the access mehod kind.
Description() string
@@ -184,6 +241,8 @@ type ValueSet interface {
ComposeSpecification(p Plugin, opts Config, config Config) error
}
+////////////////////////////////////////////////////////////////////////////////
+
// Command is the interface for a CLI command provided by a plugin.
type Command interface {
// Name of command used in the plugin.
@@ -211,3 +270,68 @@ type Command interface {
Command() *cobra.Command
}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// TransferHandler is the support interface
+// for implementing a transfer handler for the plugin support
+// library.
+// There is a standard implementation NewTransferHandler.
+type TransferHandler interface {
+ GetName() string
+ GetDescription() string
+ GetQuestions() []DecisionHandler
+}
+
+// DecisionHandler is the support interface for implementing
+// the answer to a question used for the TransferHandler.
+// A base implementation providing the non-functional attributues
+// cane be obtained by NewDecisionHandlerBase.
+type DecisionHandler interface {
+ // GetQuestion returns the name of the question answered by this handler
+ // (see common.TransferHandlerQuestions).
+ GetQuestion() string
+
+ GetDescription() string
+ // GetLabels returns the list of labels, which should be passed
+ // to the transfer handler. If nothing is specified all labels
+ // are transferred, if an empty list is given no label is handed over
+ // to the plugin command.
+ GetLabels() *[]string
+
+ // DecideOn implements the calculation of the answer to
+ // the question. The given question contains the arguments for
+ // the questions. There are three kinds of arguments:
+ // ArtifactQuestionArguments, ComponentVersionQuestionArguments and ComponentReferenceQuestionArguments.
+ // TransferHandlerQuestions maps the question name to the used
+ // argument type.
+ DecideOn(p Plugin, question QuestionArguments) (bool, error)
+}
+
+type (
+ TransferOptions = internal.TransferOptions
+ Artifact = internal.Artifact
+ AccessInfo = internal.UniformAccessSpecInfo
+ QuestionArguments = internal.QuestionArguments
+ ComponentVersionQuestionArguments = internal.ComponentVersionQuestionArguments
+ ComponentReferenceQuestionArguments = internal.ComponentReferenceQuestionArguments
+ ArtifactQuestionArguments = internal.ArtifactQuestionArguments
+ Resolution = internal.Resolution
+ DecisionRequestResult = internal.DecisionRequestResult
+)
+
+////////////////////////////////////////////////////////////////////////////////
+
+type (
+ SigningContext = internal.SigningContext
+ ConsumerProvider func(sctx signing.SigningContext) credentials.ConsumerIdentity
+)
+
+type SigningHandler interface {
+ GetName() string
+ GetDescription() string
+ GetSigner() signing.Signer
+ GetVerifier() signing.Verifier
+
+ GetConsumerProvider() ConsumerProvider
+}
diff --git a/api/ocm/plugin/ppi/plugin.go b/api/ocm/plugin/ppi/plugin.go
index 319a2891ea..7b0d5a6c74 100644
--- a/api/ocm/plugin/ppi/plugin.go
+++ b/api/ocm/plugin/ppi/plugin.go
@@ -18,8 +18,10 @@ import (
"ocm.software/ocm/api/ocm/ocmutils/registry"
"ocm.software/ocm/api/ocm/plugin/descriptor"
"ocm.software/ocm/api/utils/cobrautils"
+ "ocm.software/ocm/api/utils/cobrautils/flagsets"
"ocm.software/ocm/api/utils/errkind"
"ocm.software/ocm/api/utils/runtime"
+ inpoptions "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options"
)
type plugin struct {
@@ -39,6 +41,9 @@ type plugin struct {
methods map[string]AccessMethod
accessScheme runtime.Scheme[runtime.TypedObject, runtime.TypedObjectDecoder[runtime.TypedObject]]
+ inputs map[string]InputType
+ inputScheme runtime.Scheme[runtime.TypedObject, runtime.TypedObjectDecoder[runtime.TypedObject]]
+
actions map[string]Action
mergehandlers map[string]ValueMergeHandler
mergespecs map[string]*descriptor.LabelMergeSpecification
@@ -46,6 +51,10 @@ type plugin struct {
valuesets map[string]map[string]ValueSet
setScheme map[string]runtime.Scheme[runtime.TypedObject, runtime.TypedObjectDecoder[runtime.TypedObject]]
+ transferhandlers map[string]TransferHandler
+
+ signinghandlers map[string]SigningHandler
+
clicmds map[string]Command
configParser func(message json.RawMessage) (interface{}, error)
@@ -55,7 +64,9 @@ func NewPlugin(name string, version string) Plugin {
return &plugin{
name: name,
version: version,
+
methods: map[string]AccessMethod{},
+ inputs: map[string]InputType{},
downloaders: map[string]Downloader{},
downmappings: registry.NewRegistry[Downloader, DownloaderKey](),
@@ -63,6 +74,7 @@ func NewPlugin(name string, version string) Plugin {
uploaders: map[string]Uploader{},
upmappings: registry.NewRegistry[Uploader, UploaderKey](),
+ inputScheme: runtime.MustNewDefaultScheme[runtime.TypedObject, runtime.TypedObjectDecoder[runtime.TypedObject]](&runtime.UnstructuredVersionedTypedObject{}, false, nil),
accessScheme: runtime.MustNewDefaultScheme[runtime.TypedObject, runtime.TypedObjectDecoder[runtime.TypedObject]](&runtime.UnstructuredVersionedTypedObject{}, false, nil),
uploaderScheme: runtime.MustNewDefaultScheme[runtime.TypedObject, runtime.TypedObjectDecoder[runtime.TypedObject]](&runtime.UnstructuredVersionedTypedObject{}, false, nil),
@@ -70,6 +82,9 @@ func NewPlugin(name string, version string) Plugin {
mergehandlers: map[string]ValueMergeHandler{},
mergespecs: map[string]*descriptor.LabelMergeSpecification{},
+ transferhandlers: map[string]TransferHandler{},
+ signinghandlers: map[string]SigningHandler{},
+
valuesets: map[string]map[string]ValueSet{},
setScheme: map[string]runtime.Scheme[runtime.TypedObject, runtime.TypedObjectDecoder[runtime.TypedObject]]{},
@@ -278,21 +293,13 @@ func (p *plugin) DecodeUploadTargetSpecification(data []byte) (UploadTargetSpec,
////////////////////////////////////////////////////////////////////////////////
-func (p *plugin) RegisterAccessMethod(m AccessMethod) error {
- if p.GetAccessMethod(m.Name(), m.Version()) != nil {
- n := m.Name()
- if m.Version() != "" {
- n += runtime.VersionSeparator + m.Version()
- }
- return errors.ErrAlreadyExists(errkind.KIND_ACCESSMETHOD, n)
- }
-
+func cliOpts(reg flagsets.ConfigOptionTypeRegistry, optTypes []flagsets.ConfigOptionType) ([]CLIOption, error) {
var optlist []CLIOption
- for _, o := range m.Options() {
- known := options.DefaultRegistry.GetOptionType(o.GetName())
+ for _, o := range optTypes {
+ known := reg.GetOptionType(o.GetName())
if known != nil {
if o.ValueType() != known.ValueType() {
- return fmt.Errorf("option type %s[%s] conflicts with standard option type using value type %s", o.GetName(), o.ValueType(), known.ValueType())
+ return nil, fmt.Errorf("option type %s[%s] conflicts with standard option type using value type %s", o.GetName(), o.ValueType(), known.ValueType())
}
optlist = append(optlist, CLIOption{
Name: o.GetName(),
@@ -301,10 +308,64 @@ func (p *plugin) RegisterAccessMethod(m AccessMethod) error {
optlist = append(optlist, CLIOption{
Name: o.GetName(),
Type: o.ValueType(),
- Description: o.GetDescriptionText(),
+ Description: o.GetDescription(),
})
}
}
+ return optlist, nil
+}
+
+func (p *plugin) RegisterInputType(m InputType) error {
+ if p.GetInputType(m.Name()) != nil {
+ n := m.Name()
+ return errors.ErrAlreadyExists(descriptor.KIND_INPUTTYPE, n)
+ }
+
+ optlist, err := cliOpts(inpoptions.DefaultRegistry, m.Options())
+ if err != nil {
+ return err
+ }
+
+ inp := descriptor.InputTypeDescriptor{
+ ValueSetDefinition: descriptor.ValueSetDefinition{
+ ValueTypeDefinition: descriptor.ValueTypeDefinition{
+ Name: m.Name(),
+ Description: m.Description(),
+ Format: m.Format(),
+ },
+ CLIOptions: optlist,
+ },
+ }
+ p.descriptor.Inputs = append(p.descriptor.Inputs, inp)
+ p.inputScheme.RegisterByDecoder(m.Name(), m)
+ p.inputs[m.Name()] = m
+ return nil
+}
+
+func (p *plugin) DecodeInputSpecification(data []byte) (InputSpec, error) {
+ return p.inputScheme.Decode(data, nil)
+}
+
+func (p *plugin) GetInputType(name string) InputType {
+ return p.inputs[name]
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+func (p *plugin) RegisterAccessMethod(m AccessMethod) error {
+ if p.GetAccessMethod(m.Name(), m.Version()) != nil {
+ n := m.Name()
+ if m.Version() != "" {
+ n += runtime.VersionSeparator + m.Version()
+ }
+ return errors.ErrAlreadyExists(errkind.KIND_ACCESSMETHOD, n)
+ }
+
+ optlist, err := cliOpts(options.DefaultRegistry, m.Options())
+ if err != nil {
+ return err
+ }
+
vers := m.Version()
if vers == "" {
meth := descriptor.AccessMethodDescriptor{
@@ -321,6 +382,7 @@ func (p *plugin) RegisterAccessMethod(m AccessMethod) error {
p.methods[m.Name()] = m
vers = "v1"
}
+
meth := descriptor.AccessMethodDescriptor{
ValueSetDefinition: descriptor.ValueSetDefinition{
ValueTypeDefinition: descriptor.ValueTypeDefinition{
@@ -455,24 +517,11 @@ func (p *plugin) RegisterValueSet(s ValueSet) error {
}
}
- var optlist []CLIOption
- for _, o := range s.Options() {
- known := options.DefaultRegistry.GetOptionType(o.GetName())
- if known != nil {
- if o.ValueType() != known.ValueType() {
- return fmt.Errorf("option type %s[%s] conflicts with standard option type using value type %s", o.GetName(), o.ValueType(), known.ValueType())
- }
- optlist = append(optlist, CLIOption{
- Name: o.GetName(),
- })
- } else {
- optlist = append(optlist, CLIOption{
- Name: o.GetName(),
- Type: o.ValueType(),
- Description: o.GetDescriptionText(),
- })
- }
+ optlist, err := cliOpts(options.DefaultRegistry, s.Options())
+ if err != nil {
+ return err
}
+
vers := s.Version()
if vers == "" {
set := descriptor.ValueSetDescriptor{
@@ -597,6 +646,64 @@ func (p *plugin) Commands() []Command {
////////////////////////////////////////////////////////////////////////////////
+func (p *plugin) GetTransferHandler(name string) TransferHandler {
+ return p.transferhandlers[name]
+}
+
+func (p *plugin) RegisterTransferHandler(h TransferHandler) error {
+ if p.GetTransferHandler(h.GetName()) != nil {
+ return errors.ErrAlreadyExists("transfer handler", h.GetName())
+ }
+ d := descriptor.TransferHandlerDescriptor{
+ Name: h.GetName(),
+ Description: h.GetDescription(),
+ }
+ for _, q := range h.GetQuestions() {
+ qd := descriptor.QuestionDescriptor{
+ Question: q.GetQuestion(),
+ Description: q.GetDescription(),
+ Labels: q.GetLabels(),
+ }
+ d.Questions = append(d.Questions, qd)
+ }
+ p.descriptor.TransferHandlers = append(p.descriptor.TransferHandlers, d)
+ p.transferhandlers[h.GetName()] = h
+ return nil
+}
+
+func (p *plugin) TransferHandlers() []TransferHandler {
+ return maputils.OrderedValues(p.transferhandlers)
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+func (p *plugin) GetSigningHandler(name string) SigningHandler {
+ return p.signinghandlers[name]
+}
+
+func (p *plugin) RegisterSigningHandler(h SigningHandler) error {
+ if p.GetSigningHandler(h.GetName()) != nil {
+ return errors.ErrAlreadyExists("signing handler", h.GetName())
+ }
+ d := descriptor.SigningHandlerDescriptor{
+ Name: h.GetName(),
+ Description: h.GetDescription(),
+ Credentials: h.GetConsumerProvider() != nil,
+ Signer: h.GetSigner() != nil,
+ Verifier: h.GetVerifier() != nil,
+ }
+
+ p.signinghandlers[h.GetName()] = h
+ p.descriptor.SigningHandlers = append(p.descriptor.SigningHandlers, d)
+ return nil
+}
+
+func (p *plugin) SigningHandlers() []SigningHandler {
+ return maputils.OrderedValues(p.signinghandlers)
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
func (p *plugin) GetConfigType(name string) *descriptor.ConfigTypeDescriptor {
var def *descriptor.ConfigTypeDescriptor
for _, d := range p.descriptor.ConfigTypes {
diff --git a/api/ocm/plugin/ppi/questions.go b/api/ocm/plugin/ppi/questions.go
new file mode 100644
index 0000000000..368c47477b
--- /dev/null
+++ b/api/ocm/plugin/ppi/questions.go
@@ -0,0 +1,16 @@
+package ppi
+
+import (
+ "ocm.software/ocm/api/ocm/plugin/internal"
+)
+
+const (
+ Q_UPDATE_VERSION = internal.Q_UPDATE_VERSION
+ Q_OVERWRITE_VERSION = internal.Q_OVERWRITE_VERSION
+ Q_ENFORCE_TRANSPORT = internal.Q_ENFORCE_TRANSPORT
+ Q_TRANSFER_VERSION = internal.Q_TRANSFER_VERSION
+ Q_TRANSFER_RESOURCE = internal.Q_TRANSFER_RESOURCE
+ Q_TRANSFER_SOURCE = internal.Q_TRANSFER_SOURCE
+)
+
+var TransferHandlerQuestions = internal.TransferHandlerQuestions
diff --git a/api/ocm/plugin/ppi/utils.go b/api/ocm/plugin/ppi/utils.go
index 27b0885d73..d20c19600d 100644
--- a/api/ocm/plugin/ppi/utils.go
+++ b/api/ocm/plugin/ppi/utils.go
@@ -4,37 +4,70 @@ import (
"encoding/json"
"reflect"
- "github.com/pkg/errors"
+ "github.com/mandelsoft/goutils/errors"
+ "github.com/mandelsoft/goutils/generics"
"golang.org/x/exp/slices"
- "ocm.software/ocm/api/ocm/extensions/accessmethods/options"
+ "ocm.software/ocm/api/ocm/plugin/descriptor"
+ "ocm.software/ocm/api/tech/signing"
+ "ocm.software/ocm/api/utils/cobrautils/flagsets"
"ocm.software/ocm/api/utils/runtime"
)
type decoder runtime.TypedObjectDecoder[runtime.TypedObject]
-type AccessMethodBase struct {
+type blobProviderBase struct {
decoder
nameDescription
- version string
- format string
+ format string
}
-func MustNewAccessMethodBase(name, version string, proto AccessSpec, desc string, format string) AccessMethodBase {
+func MustNewBlobProviderBase(name string, proto AccessSpec, desc string, format string) blobProviderBase {
decoder, err := runtime.NewDirectDecoder(proto)
if err != nil {
panic(err)
}
- return AccessMethodBase{
+ return blobProviderBase{
decoder: decoder,
nameDescription: nameDescription{
name: name,
desc: desc,
},
- version: version,
- format: format,
+ format: format,
+ }
+}
+
+func (b *AccessMethodBase) BlobProviderBase() string {
+ return b.format
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+type InputTypeBase struct {
+ blobProviderBase
+}
+
+func MustNewInputTypeBase(name string, proto InputSpec, desc string, format string) InputTypeBase {
+ return InputTypeBase{MustNewBlobProviderBase(name, proto, desc, format)}
+}
+
+func (b *InputTypeBase) Format() string {
+ return b.format
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+type AccessMethodBase struct {
+ blobProviderBase
+ version string
+}
+
+func MustNewAccessMethodBase(name, version string, proto AccessSpec, desc string, format string) AccessMethodBase {
+ return AccessMethodBase{
+ blobProviderBase: MustNewBlobProviderBase(name, proto, desc, format),
+ version: version,
}
}
@@ -115,6 +148,192 @@ func (b *nameDescription) Description() string {
////////////////////////////////////////////////////////////////////////////////
+type transferHandler struct {
+ name string
+ description string
+ questions []DecisionHandler
+}
+
+func NewTransferHandler(name, desc string) *transferHandler {
+ return &transferHandler{
+ name: name,
+ description: desc,
+ questions: nil,
+ }
+}
+
+func (t *transferHandler) GetName() string {
+ return t.name
+}
+
+func (t *transferHandler) GetDescription() string {
+ return t.description
+}
+
+func (t *transferHandler) GetQuestions() []DecisionHandler {
+ return t.questions
+}
+
+func (t *transferHandler) RegisterDecision(h DecisionHandler) error {
+ if TransferHandlerQuestions[h.GetQuestion()] == nil {
+ return errors.ErrInvalid(descriptor.KIND_QUESTION, h.GetQuestion())
+ }
+ for _, e := range t.questions {
+ if e.GetQuestion() == h.GetQuestion() {
+ return errors.ErrAlreadyExists(descriptor.KIND_QUESTION, e.GetQuestion())
+ }
+ }
+ t.questions = append(t.questions, h)
+ return nil
+}
+
+// DecisionHandlerBase provides access to the
+// non-functional attributes of a DecisionHandler.
+// It can be created with NewDecisionHandlerBase and
+// embedded into the final DecisionHandler implementation.
+type DecisionHandlerBase struct {
+ question string
+ description string
+ labels *[]string
+}
+
+func (d *DecisionHandlerBase) GetQuestion() string {
+ return d.question
+}
+
+func (d *DecisionHandlerBase) GetDescription() string {
+ return d.description
+}
+
+func (d *DecisionHandlerBase) GetLabels() *[]string {
+ return d.labels
+}
+
+func NewDecisionHandlerBase(q, desc string, labels ...string) DecisionHandlerBase {
+ return DecisionHandlerBase{q, desc, generics.Pointer(slices.Clone(labels))}
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+type QuestionResultFunc func(p Plugin, question QuestionArguments) (bool, error)
+
+func ComponentVersionQuestionFunc(f func(p Plugin, question *ComponentVersionQuestionArguments) (bool, error)) QuestionResultFunc {
+ return func(p Plugin, question QuestionArguments) (bool, error) {
+ return f(p, question.(*ComponentVersionQuestionArguments))
+ }
+}
+
+type ComponentReferenceQuestionFunc = func(p Plugin, question *ComponentReferenceQuestionArguments) (bool, error)
+
+func ForComponentReferenceQuestion(f func(p Plugin, question *ComponentReferenceQuestionArguments) (bool, error)) QuestionResultFunc {
+ return func(p Plugin, question QuestionArguments) (bool, error) {
+ return f(p, question.(*ComponentReferenceQuestionArguments))
+ }
+}
+
+type ArtifactQuestionFunc = func(p Plugin, question *ArtifactQuestionArguments) (bool, error)
+
+func ForArtifactQuestion(f ArtifactQuestionFunc) QuestionResultFunc {
+ return func(p Plugin, question QuestionArguments) (bool, error) {
+ return f(p, question.(*ArtifactQuestionArguments))
+ }
+}
+
+type defaultDecisionHandler struct {
+ DecisionHandlerBase
+ handler func(p Plugin, question QuestionArguments) (bool, error)
+}
+
+// NewDecisionHandler provides a default decision handler based on its standard
+// fields and a handler function.
+func NewDecisionHandler(q, desc string, h QuestionResultFunc, labels ...string) DecisionHandler {
+ return &defaultDecisionHandler{
+ DecisionHandlerBase: NewDecisionHandlerBase(q, desc, labels...),
+ handler: h,
+ }
+}
+
+func (d *defaultDecisionHandler) DecideOn(p Plugin, question QuestionArguments) (bool, error) {
+ return d.handler(p, question)
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// specialized handler creation
+
+func NewTransferResourceDecision(desc string, h ArtifactQuestionFunc, labels ...string) DecisionHandler {
+ return NewDecisionHandler(Q_TRANSFER_RESOURCE, desc, ForArtifactQuestion(h))
+}
+
+func NewTransferSourceDecision(desc string, h ArtifactQuestionFunc, labels ...string) DecisionHandler {
+ return NewDecisionHandler(Q_TRANSFER_SOURCE, desc, ForArtifactQuestion(h))
+}
+
+func NewEnforceTransportDesision(desc string, h ComponentReferenceQuestionFunc, labels ...string) DecisionHandler {
+ return NewDecisionHandler(Q_ENFORCE_TRANSPORT, desc, ForComponentReferenceQuestion(h))
+}
+
+func NewTransferVersionDecision(desc string, h ComponentReferenceQuestionFunc, labels ...string) DecisionHandler {
+ return NewDecisionHandler(Q_TRANSFER_VERSION, desc, ForComponentReferenceQuestion(h))
+}
+
+func NewOverwriteVersionDecision(desc string, h ComponentReferenceQuestionFunc, labels ...string) DecisionHandler {
+ return NewDecisionHandler(Q_OVERWRITE_VERSION, desc, ForComponentReferenceQuestion(h))
+}
+
+func NewUpdateVersionDecision(desc string, h ComponentReferenceQuestionFunc, labels ...string) DecisionHandler {
+ return NewDecisionHandler(Q_UPDATE_VERSION, desc, ForComponentReferenceQuestion(h))
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+type signingHandler struct {
+ name string
+ description string
+ consumer ConsumerProvider
+ signer signing.Signer
+ verifier signing.Verifier
+}
+
+func NewSigningHandler(name, desc string, signer signing.Signer) *signingHandler {
+ return &signingHandler{
+ name: name,
+ description: desc,
+ signer: signer,
+ }
+}
+
+func (s *signingHandler) WithVerifier(verifier signing.Verifier) *signingHandler {
+ s.verifier = verifier
+ return s
+}
+
+func (s *signingHandler) WithCredentials(p ConsumerProvider) *signingHandler {
+ s.consumer = p
+ return s
+}
+
+func (t *signingHandler) GetName() string {
+ return t.name
+}
+
+func (t *signingHandler) GetDescription() string {
+ return t.description
+}
+
+func (t *signingHandler) GetSigner() signing.Signer {
+ return t.signer
+}
+
+func (t *signingHandler) GetVerifier() signing.Verifier {
+ return t.verifier
+}
+
+func (t *signingHandler) GetConsumerProvider() ConsumerProvider {
+ return t.consumer
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
// Config is a generic structured config stored in a string map.
type Config map[string]interface{}
@@ -123,7 +342,7 @@ func (c Config) GetValue(name string) (interface{}, bool) {
return v, ok
}
-func (c Config) ConvertFor(list ...options.OptionType) error {
+func (c Config) ConvertFor(list ...flagsets.ConfigOptionType) error {
for _, o := range list {
if v, ok := c[o.GetName()]; ok {
t := reflect.TypeOf(o.Create().Value())
diff --git a/api/ocm/plugin/registration/registration.go b/api/ocm/plugin/registration/registration.go
index eb8f2da76c..fae76ff997 100644
--- a/api/ocm/plugin/registration/registration.go
+++ b/api/ocm/plugin/registration/registration.go
@@ -11,6 +11,7 @@ import (
pluginaccess "ocm.software/ocm/api/ocm/extensions/accessmethods/plugin"
pluginaction "ocm.software/ocm/api/ocm/extensions/actionhandler/plugin"
"ocm.software/ocm/api/ocm/extensions/attrs/plugincacheattr"
+ "ocm.software/ocm/api/ocm/extensions/attrs/signingattr"
pluginupload "ocm.software/ocm/api/ocm/extensions/blobhandler/handlers/generic/plugin"
"ocm.software/ocm/api/ocm/extensions/download"
plugindownload "ocm.software/ocm/api/ocm/extensions/download/handlers/plugin"
@@ -20,6 +21,7 @@ import (
"ocm.software/ocm/api/ocm/valuemergehandler"
pluginmerge "ocm.software/ocm/api/ocm/valuemergehandler/handlers/plugin"
"ocm.software/ocm/api/ocm/valuemergehandler/hpi"
+ sigplugin "ocm.software/ocm/api/tech/signing/handlers/plugin"
"ocm.software/ocm/api/utils/runtime"
)
@@ -149,6 +151,17 @@ func RegisterExtensions(ctxp ocm.ContextProvider) error {
t := plugin.New(name, s.Description)
registry.Register(t)
}
+
+ signers := signingattr.Get(ctx).HandlerRegistry()
+ for _, s := range p.GetDescriptor().SigningHandlers {
+ if s.Signer {
+ signers.RegisterSigner(s.Name, sigplugin.NewSigner(p, s.Name))
+ }
+ if s.Verifier {
+ signers.RegisterVerifier(s.Name, sigplugin.NewVerifier(p, s.Name))
+ }
+ }
}
+
return nil
}
diff --git a/api/ocm/plugin/utils.go b/api/ocm/plugin/utils.go
index f3f15ae8de..05a1195e4f 100644
--- a/api/ocm/plugin/utils.go
+++ b/api/ocm/plugin/utils.go
@@ -2,6 +2,7 @@ package plugin
import (
"encoding/json"
+ "io"
"github.com/opencontainers/go-digest"
@@ -10,19 +11,27 @@ import (
"ocm.software/ocm/api/utils/iotools"
)
-type AccessDataWriter struct {
- plugin Plugin
- creds json.RawMessage
- accspec json.RawMessage
+type BlobAccessWriter struct {
+ creds json.RawMessage
+ spec json.RawMessage
+ getter func(writer io.Writer, creds json.RawMessage, spec json.RawMessage) error
}
-func NewAccessDataWriter(p Plugin, creds, accspec json.RawMessage) *AccessDataWriter {
- return &AccessDataWriter{p, creds, accspec}
+func NewAccessDataWriter(p Plugin, creds, spec json.RawMessage) *BlobAccessWriter {
+ return &BlobAccessWriter{creds, spec, p.Get}
}
-func (d *AccessDataWriter) WriteTo(w accessio.Writer) (int64, digest.Digest, error) {
+func NewInputDataWriter(p Plugin, dir string, creds, spec json.RawMessage) *BlobAccessWriter {
+ return &BlobAccessWriter{
+ creds: creds,
+ spec: spec,
+ getter: func(w io.Writer, creds, spec json.RawMessage) error { return p.GetInputBlob(w, dir, creds, spec) },
+ }
+}
+
+func (d *BlobAccessWriter) WriteTo(w accessio.Writer) (int64, digest.Digest, error) {
dw := iotools.NewDefaultDigestWriter(accessio.NopWriteCloser(w))
- err := d.plugin.Get(dw, d.creds, d.accspec)
+ err := d.getter(dw, d.creds, d.spec)
if err != nil {
return blobaccess.BLOB_UNKNOWN_SIZE, blobaccess.BLOB_UNKNOWN_DIGEST, err
}
diff --git a/api/ocm/ref.go b/api/ocm/ref.go
index 72f0b08e84..4f73eef47a 100644
--- a/api/ocm/ref.go
+++ b/api/ocm/ref.go
@@ -145,14 +145,6 @@ func (r *RefSpec) Name() string {
return fmt.Sprintf("%s/%s//%s", r.Host, r.SubPath, r.Component)
}
-func (r *RefSpec) HostPort() (string, string) {
- i := strings.Index(r.Host, ":")
- if i < 0 {
- return r.Host, ""
- }
- return r.Host[:i], r.Host[i+1:]
-}
-
func (r *RefSpec) Reference() string {
t := r.Type
if t != "" {
diff --git a/api/ocm/tools/transfer/init.go b/api/ocm/tools/transfer/init.go
index a68ad2ce93..26d7b5e1a3 100644
--- a/api/ocm/tools/transfer/init.go
+++ b/api/ocm/tools/transfer/init.go
@@ -2,6 +2,7 @@ package transfer
import (
_ "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/config"
+ _ "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/plugin"
_ "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/spiff"
_ "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard"
)
diff --git a/api/ocm/tools/transfer/options.go b/api/ocm/tools/transfer/options.go
index 4c9e66dcf2..cd1d688ca6 100644
--- a/api/ocm/tools/transfer/options.go
+++ b/api/ocm/tools/transfer/options.go
@@ -41,6 +41,9 @@ type localOptions struct {
func (opts *localOptions) Eval(optlist ...transferhandler.TransferOption) error {
for _, o := range optlist {
+ if o == nil {
+ continue
+ }
if _, ok := o.(transferhandler.TransferOptionsCreator); !ok {
err := o.ApplyTransferOption(opts)
if err != nil {
diff --git a/api/ocm/tools/transfer/transferhandler/options.go b/api/ocm/tools/transfer/transferhandler/options.go
new file mode 100644
index 0000000000..cf290daaa8
--- /dev/null
+++ b/api/ocm/tools/transfer/transferhandler/options.go
@@ -0,0 +1,39 @@
+package transferhandler
+
+import (
+ "github.com/mandelsoft/goutils/errors"
+)
+
+//////////////////////////////////////////////////////////////////////////////
+
+// ConfigOptionConsumer is an interface for option sets supporting generic
+// non-standard options.
+// Specialized option set implementations map such generic
+// config to their specialized settings. The format depends
+// on the option target. For example, for spiff it is a spiff
+// script.
+type ConfigOptionConsumer interface {
+ SetConfig([]byte)
+ GetConfig() []byte
+}
+
+type configOption struct {
+ config []byte
+}
+
+func (o *configOption) ApplyTransferOption(to TransferOptions) error {
+ if eff, ok := to.(ConfigOptionConsumer); ok {
+ eff.SetConfig(o.config)
+ return nil
+ } else {
+ return errors.ErrNotSupported(KIND_TRANSFEROPTION, "config")
+ }
+}
+
+// WithConfig configures a handler specific configuration.
+// It is accepted by all handlers featuring such a config possibility.
+func WithConfig(config []byte) TransferOption {
+ return &configOption{
+ config: config,
+ }
+}
diff --git a/api/ocm/tools/transfer/transferhandler/plugin/handler.go b/api/ocm/tools/transfer/transferhandler/plugin/handler.go
new file mode 100644
index 0000000000..74116faf69
--- /dev/null
+++ b/api/ocm/tools/transfer/transferhandler/plugin/handler.go
@@ -0,0 +1,408 @@
+package plugin
+
+import (
+ "encoding/json"
+ "sync"
+
+ "github.com/mandelsoft/goutils/errors"
+ "github.com/mandelsoft/goutils/sliceutils"
+ "golang.org/x/exp/slices"
+
+ "ocm.software/ocm/api/ocm"
+ "ocm.software/ocm/api/ocm/compdesc"
+ metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1"
+ v2 "ocm.software/ocm/api/ocm/compdesc/versions/v2"
+ "ocm.software/ocm/api/ocm/extensions/attrs/plugincacheattr"
+ "ocm.software/ocm/api/ocm/plugin"
+ "ocm.software/ocm/api/ocm/plugin/descriptor"
+ "ocm.software/ocm/api/ocm/plugin/ppi"
+ "ocm.software/ocm/api/ocm/resolvers"
+ "ocm.software/ocm/api/ocm/tools/transfer/transferhandler"
+ "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard"
+ "ocm.software/ocm/api/utils/refmgmt"
+ "ocm.software/ocm/api/utils/runtime"
+)
+
+type Handler struct {
+ lock sync.Mutex
+ standard.Handler
+ opts *Options
+ plugin plugin.Plugin
+ desc *descriptor.TransferHandlerDescriptor
+}
+
+func New(opts ...transferhandler.TransferOption) (transferhandler.TransferHandler, error) {
+ options := &Options{}
+ err := transferhandler.ApplyOptions(options, opts...)
+ if err != nil {
+ return nil, err
+ }
+
+ return &Handler{
+ Handler: *standard.NewDefaultHandler(&options.Options),
+ opts: options,
+ }, nil
+}
+
+func (h *Handler) GetConfig() []byte {
+ return h.opts.GetConfig()
+}
+
+func (h *Handler) ResolvePlugin(ctx ocm.ContextProvider) (plugin.Plugin, error) {
+ h.lock.Lock()
+ defer h.lock.Unlock()
+
+ if h.plugin == nil {
+ pi := plugincacheattr.Get(ctx)
+ if pi == nil {
+ return nil, errors.ErrUnknown(plugin.KIND_PLUGIN, h.opts.plugin)
+ }
+ p := pi.Get(h.opts.plugin)
+ if p == nil {
+ return nil, errors.ErrUnknown(plugin.KIND_PLUGIN, h.opts.plugin)
+ }
+ h.plugin = p
+ h.desc = p.GetDescriptor().TransferHandlers.Get(h.opts.GetTransferHandler())
+ if h.desc == nil {
+ return nil, errors.ErrUnknown(plugin.KIND_TRANSFERHANDLER, h.opts.plugin)
+ }
+ }
+ return h.plugin, nil
+}
+
+func filterLabels(in metav1.Labels, filter *[]string) metav1.Labels {
+ var r metav1.Labels
+
+ if filter == nil {
+ return in.Copy()
+ }
+ if len(*filter) > 0 {
+ for _, l := range in {
+ if filter == nil || slices.Contains(*filter, l.Name) {
+ r = append(r, l)
+ }
+ }
+ }
+ return r
+}
+
+func (h *Handler) transferOptions() (*plugin.TransferOptions, error) {
+ var special *json.RawMessage
+
+ if h.opts.config != nil {
+ s, err := runtime.ToJSON(h.opts.config)
+ if err != nil {
+ return nil, err
+ }
+ special = &s
+ }
+ return &plugin.TransferOptions{
+ Recursive: h.opts.GetRecursive(),
+ ResourcesByValue: h.opts.GetResourcesByValue(),
+ LocalByValue: h.opts.GetLocalResourcesByValue(),
+ SourcesByValue: h.opts.GetSourcesByValue(),
+ KeepGlobalAccess: h.opts.GetKeepGlobalAccess(),
+ StopOnExisting: h.opts.GetStopOnExistingVersion(),
+ EnforceTransport: h.opts.GetEnforceTransport(),
+ Overwrite: h.opts.GetOverwrite(),
+ SkipUpdate: h.opts.GetSkipUpdate(),
+ OmitAccessTypes: h.opts.GetOmittedAccessTypes(),
+ OmitArtifactTypes: h.opts.GetOmittedArtifactTypes(),
+ Special: special,
+ }, nil
+}
+
+func (h *Handler) sourceComponentVersion(src ocm.ComponentVersionAccess, question string) (*plugin.SourceComponentVersion, error) {
+ repo := src.Repository()
+ defer repo.Close()
+
+ repospec, err := ocm.ToGenericRepositorySpec(repo.GetSpecification())
+ if err != nil {
+ return nil, err
+ }
+
+ filter := h.desc.GetQuestion(question).Labels
+
+ return &plugin.SourceComponentVersion{
+ Name: src.GetName(),
+ Version: src.GetVersion(),
+ Provider: metav1.Provider{
+ Name: src.GetProvider().GetName(),
+ Labels: filterLabels(src.GetProvider().Labels, filter),
+ },
+ Repository: *repospec,
+ Labels: filterLabels(src.GetDescriptor().Labels, filter),
+ }, nil
+}
+
+func (h *Handler) respositoryTarget(tgt ocm.ComponentVersionAccess) (*plugin.TargetRepositorySpec, error) {
+ repo := tgt.Repository()
+ defer repo.Close()
+
+ return ocm.ToGenericRepositorySpec(repo.GetSpecification())
+}
+
+func (h *Handler) artifact(ctx ocm.Context, art ocm.AccessSpec, ra *compdesc.ElementMeta, question string) (*plugin.Artifact, error) {
+ filter := h.desc.GetQuestion(question).Labels
+
+ a := art.Info(ctx)
+ s, err := ocm.ToGenericAccessSpec(art)
+ if err != nil {
+ return nil, err
+ }
+
+ return &plugin.Artifact{
+ Meta: v2.ElementMeta{
+ Name: ra.Name,
+ Version: ra.Version,
+ ExtraIdentity: ra.ExtraIdentity.Copy(),
+ Labels: filterLabels(ra.Labels, filter),
+ },
+ Access: *s,
+ AccessInfo: *a,
+ }, nil
+}
+
+func (h *Handler) askComponentVersionQuestion(question string, src ocm.ComponentVersionAccess, tgt ocm.ComponentVersionAccess) (bool, error) {
+ p, err := h.ResolvePlugin(src.GetContext())
+ if err != nil {
+ return false, err
+ }
+
+ s, err := h.sourceComponentVersion(src, question)
+ if err != nil {
+ return false, err
+ }
+ t, err := h.respositoryTarget(tgt)
+ if err != nil {
+ return false, err
+ }
+ o, err := h.transferOptions()
+ if err != nil {
+ return false, err
+ }
+
+ args := plugin.ComponentVersionQuestionArguments{
+ Source: *s,
+ Target: *t,
+ Options: *o,
+ }
+
+ r, err := p.AskTransferQuestion(h.opts.handler, question, args)
+ if err != nil {
+ return false, err
+ }
+ return r.Decision, nil
+}
+
+func (h *Handler) askArtifactQuestion(question string, src ocm.ComponentVersionAccess, art ocm.AccessSpec, ra *compdesc.ElementMeta) (bool, error) {
+ p, err := h.ResolvePlugin(src.GetContext())
+ if err != nil {
+ return false, err
+ }
+
+ s, err := h.sourceComponentVersion(src, question)
+ if err != nil {
+ return false, err
+ }
+ a, err := h.artifact(src.GetContext(), art, ra, question)
+ if err != nil {
+ return false, err
+ }
+ o, err := h.transferOptions()
+ if err != nil {
+ return false, err
+ }
+
+ args := plugin.ArtifactQuestionArguments{
+ Source: *s,
+ Artifact: *a,
+ Options: *o,
+ }
+
+ r, err := p.AskTransferQuestion(h.opts.handler, question, args)
+ if err != nil {
+ return false, err
+ }
+ return r.Decision, nil
+}
+
+func (h *Handler) UpdateVersion(src ocm.ComponentVersionAccess, tgt ocm.ComponentVersionAccess) (bool, error) {
+ _, err := h.ResolvePlugin(src.GetContext())
+ if err != nil {
+ return false, err
+ }
+ if h.desc.GetQuestion(plugin.Q_UPDATE_VERSION) == nil {
+ return h.Handler.UpdateVersion(src, tgt)
+ }
+ return h.askComponentVersionQuestion(plugin.Q_UPDATE_VERSION, src, tgt)
+}
+
+func (h *Handler) EnforceTransport(src ocm.ComponentVersionAccess, tgt ocm.ComponentVersionAccess) (bool, error) {
+ _, err := h.ResolvePlugin(src.GetContext())
+ if err != nil {
+ return false, err
+ }
+ if h.desc.GetQuestion(plugin.Q_ENFORCE_TRANSPORT) == nil {
+ return h.Handler.EnforceTransport(src, tgt)
+ }
+ return h.askComponentVersionQuestion(plugin.Q_ENFORCE_TRANSPORT, src, tgt)
+}
+
+func (h *Handler) OverwriteVersion(src ocm.ComponentVersionAccess, tgt ocm.ComponentVersionAccess) (bool, error) {
+ _, err := h.ResolvePlugin(src.GetContext())
+ if err != nil {
+ return false, err
+ }
+ if h.desc.GetQuestion(plugin.Q_OVERWRITE_VERSION) == nil {
+ return h.Handler.OverwriteVersion(src, tgt)
+ }
+ return h.askComponentVersionQuestion(plugin.Q_OVERWRITE_VERSION, src, tgt)
+}
+
+func (h *Handler) TransferVersion(repo ocm.Repository, src ocm.ComponentVersionAccess, meta *compdesc.Reference, tgt ocm.Repository) (ocm.ComponentVersionAccess, transferhandler.TransferHandler, error) {
+ _, err := h.ResolvePlugin(src.GetContext())
+ if err != nil {
+ return nil, nil, err
+ }
+ if h.desc.GetQuestion(plugin.Q_TRANSFER_VERSION) == nil || src == nil {
+ return h.Handler.TransferVersion(repo, src, meta, tgt)
+ }
+
+ filter := h.desc.GetQuestion(plugin.Q_TRANSFER_VERSION).Labels
+ p, err := h.ResolvePlugin(src.GetContext())
+ if err != nil {
+ return nil, nil, err
+ }
+
+ s, err := h.sourceComponentVersion(src, plugin.Q_TRANSFER_VERSION)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ t, err := ocm.ToGenericRepositorySpec(repo.GetSpecification())
+ if err != nil {
+ return nil, nil, err
+ }
+ o, err := h.transferOptions()
+ if err != nil {
+ return nil, nil, err
+ }
+
+ args := plugin.ComponentReferenceQuestionArguments{
+ Source: *s,
+ Target: *t,
+ ElementMeta: v2.ElementMeta{
+ Name: meta.Name,
+ Version: meta.Version,
+ ExtraIdentity: meta.ExtraIdentity.Copy(),
+ Labels: filterLabels(meta.Labels, filter),
+ },
+ Options: *o,
+ }
+
+ r, err := p.AskTransferQuestion(h.opts.handler, plugin.Q_TRANSFER_VERSION, args)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // TODO: evaluate transfer handler and repo
+
+ if h.opts.IsStopOnExistingVersion() && tgt != nil {
+ if found, err := tgt.ExistsComponentVersion(meta.ComponentName, meta.Version); found || err != nil {
+ return nil, nil, errors.Wrapf(err, "failed looking up in target")
+ }
+ }
+
+ if !r.Decision {
+ return nil, nil, nil
+ }
+
+ if r.Resolution != nil {
+ // calculate transfer context for referenced component.
+ var opts sliceutils.Slice[transferhandler.TransferOption]
+
+ handler := transferhandler.TransferHandler(h)
+ if r.Resolution.TransferOptions != nil {
+ opts = transferOptions(r.Resolution.TransferOptions)
+ handler, err = New(opts...)
+ if err != nil {
+ return nil, nil, errors.Wrapf(err, "option mismatch for plugin handler")
+ }
+ } else {
+ opts.Add(h.opts)
+ }
+
+ if r.Resolution.TransferHandler != "" {
+ // override handler
+ handler, err = transferhandler.For(src.GetContext()).ByName(src.GetContext(), r.Resolution.TransferHandler, opts...)
+ if err != nil {
+ return nil, nil, errors.Wrapf(err, "invalid transfer handler %q", r.Resolution.TransferHandler)
+ }
+ }
+
+ if r.Resolution.RepositorySpec != nil {
+ repo, err = refmgmt.ToLazy(src.GetContext().RepositoryForSpec(r.Resolution.RepositorySpec))
+ if err != nil {
+ return nil, nil, err
+ }
+ defer repo.Close()
+ }
+ compoundResolver := resolvers.NewCompoundResolver(repo, h.opts.GetResolver())
+ cv, err := compoundResolver.LookupComponentVersion(meta.GetComponentName(), meta.Version)
+ return cv, handler, err
+ }
+ return h.Handler.TransferVersion(repo, src, meta, tgt)
+}
+
+func (h *Handler) TransferResource(src ocm.ComponentVersionAccess, a ocm.AccessSpec, r ocm.ResourceAccess) (bool, error) {
+ _, err := h.ResolvePlugin(src.GetContext())
+ if err != nil {
+ return false, err
+ }
+ if h.desc.GetQuestion(plugin.Q_TRANSFER_RESOURCE) == nil {
+ return h.Handler.TransferResource(src, a, r)
+ }
+ return h.askArtifactQuestion(ppi.Q_TRANSFER_RESOURCE, src, a, &r.Meta().ElementMeta)
+}
+
+func (h *Handler) TransferSource(src ocm.ComponentVersionAccess, a ocm.AccessSpec, r ocm.SourceAccess) (bool, error) {
+ _, err := h.ResolvePlugin(src.GetContext())
+ if err != nil {
+ return false, err
+ }
+ if h.desc.GetQuestion(plugin.Q_TRANSFER_SOURCE) == nil {
+ return h.Handler.TransferSource(src, a, r)
+ }
+ return h.askArtifactQuestion(ppi.Q_TRANSFER_SOURCE, src, a, &r.Meta().ElementMeta)
+}
+
+func transferOptions(o *plugin.TransferOptions) []transferhandler.TransferOption {
+ var opts sliceutils.Slice[transferhandler.TransferOption]
+ apply(&opts, o.KeepGlobalAccess, standard.KeepGlobalAccess)
+ apply(&opts, o.ResourcesByValue, standard.ResourcesByValue)
+ apply(&opts, o.SourcesByValue, standard.SourcesByValue)
+ apply(&opts, o.EnforceTransport, standard.EnforceTransport)
+ apply(&opts, o.LocalByValue, standard.LocalResourcesByValue)
+ apply(&opts, o.Overwrite, standard.Overwrite)
+ apply(&opts, o.Recursive, standard.Recursive)
+ apply(&opts, o.StopOnExisting, standard.StopOnExistingVersion)
+ apply(&opts, o.SkipUpdate, standard.SkipUpdate)
+
+ if o.OmitAccessTypes != nil {
+ opts.Add(standard.OmitAccessTypes(o.OmitAccessTypes...))
+ }
+ if o.OmitArtifactTypes != nil {
+ opts.Add(standard.OmitAccessTypes(o.OmitArtifactTypes...))
+ }
+ if o.Special != nil {
+ opts.Add(transferhandler.WithConfig(*o.Special))
+ }
+ return opts
+}
+
+func apply[T any, O any](opts *sliceutils.Slice[O], o *T, c func(...T) O) {
+ if o != nil {
+ opts.Add(c(*o))
+ }
+}
diff --git a/api/ocm/tools/transfer/transferhandler/plugin/handler_test.go b/api/ocm/tools/transfer/transferhandler/plugin/handler_test.go
new file mode 100644
index 0000000000..50c7fa84e7
--- /dev/null
+++ b/api/ocm/tools/transfer/transferhandler/plugin/handler_test.go
@@ -0,0 +1,186 @@
+//go:build unix
+
+package plugin_test
+
+import (
+ "os"
+ "reflect"
+
+ . "github.com/mandelsoft/goutils/testutils"
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+ . "ocm.software/ocm/api/ocm/plugin/testutils"
+ . "ocm.software/ocm/cmds/ocm/testhelper"
+
+ "ocm.software/ocm/api/ocm"
+ "ocm.software/ocm/api/ocm/compdesc"
+ metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1"
+ "ocm.software/ocm/api/ocm/cpi"
+ "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact"
+ "ocm.software/ocm/api/ocm/extensions/attrs/plugincacheattr"
+ "ocm.software/ocm/api/ocm/extensions/repositories/ocireg"
+ "ocm.software/ocm/api/ocm/internal"
+ "ocm.software/ocm/api/ocm/tools/transfer/transferhandler"
+ "ocm.software/ocm/api/utils/blobaccess"
+
+ "github.com/mandelsoft/goutils/generics"
+
+ "ocm.software/ocm/api/ocm/tools/transfer"
+ "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/plugin"
+)
+
+const PLUGIN = "transferplugin"
+const HANDLER = "demo"
+
+var _ = Describe("Plugin Handler Test Environment", func() {
+ Context("handler creation", func() {
+ It("", func() {
+ f := Must(transfer.NewTransferHandler(plugin.Plugin(PLUGIN), plugin.TransferHandler(HANDLER)))
+ Expect(reflect.TypeOf(f)).To(Equal(generics.TypeOf[*plugin.Handler]()))
+ })
+ })
+
+ Context("plugin execution", func() {
+ var env *TestEnv
+ var plugins TempPluginDir
+
+ BeforeEach(func() {
+ env = NewTestEnv(TestData())
+ plugins = Must(ConfigureTestPlugins(env, "testdata/plugins"))
+ })
+
+ AfterEach(func() {
+ plugins.Cleanup()
+ env.Cleanup()
+ })
+
+ It("loads plugin", func() {
+ registry := plugincacheattr.Get(env)
+ // Expect(registration.RegisterExtensions(env)).To(Succeed())
+ p := registry.Get(PLUGIN)
+ Expect(p).NotTo(BeNil())
+ Expect(p.Error()).To(Equal(""))
+ })
+
+ It("answers question", func() {
+ config := Must(os.ReadFile("testdata/config"))
+ f := Must(transfer.NewTransferHandler(plugin.Plugin(PLUGIN), plugin.TransferHandler(HANDLER), plugin.TransferHandlerConfig(config)))
+
+ cv := FakeCV(env.OCMContext())
+
+ ra := &ResourceAccess{
+ ElementMeta: compdesc.ElementMeta{
+ Name: "test",
+ Version: "1.0.0",
+ },
+ }
+ b := Must(f.TransferResource(cv, ociartifact.New("ghcr.io/open-component-model/test:v1"), ra))
+ Expect(b).To(BeTrue())
+
+ b = Must(f.TransferResource(cv, ociartifact.New("gcr.io/open-component-model/test:v1"), ra))
+ Expect(b).To(BeFalse())
+ })
+
+ Context("registrations", func() {
+ It("finds plugin handler by name", func() {
+ h := Must(transferhandler.For(env).ByName(env, "plugin/transferplugin/demo"))
+ Expect(reflect.TypeOf(h)).To(Equal(generics.TypeOf[*plugin.Handler]()))
+ })
+
+ It("finds plugin handler by name and config", func() {
+ config := Must(os.ReadFile("testdata/config"))
+ h := Must(transferhandler.For(env).ByName(env, "plugin/transferplugin/demo", transferhandler.WithConfig(config)))
+ Expect(reflect.TypeOf(h)).To(Equal(generics.TypeOf[*plugin.Handler]()))
+ Expect(h.(*plugin.Handler).GetConfig()).To(YAMLEqual(config))
+ })
+ })
+ })
+})
+
+////////////////////////////////////////////////////////////////////////////////
+
+type ComponentVersionAccess struct {
+ cpi.DummyComponentVersionAccess
+}
+
+func FakeCV(ctx ocm.Context) ocm.ComponentVersionAccess {
+ return &ComponentVersionAccess{
+ cpi.DummyComponentVersionAccess{ctx},
+ }
+}
+
+func (a *ComponentVersionAccess) Repository() ocm.Repository {
+ r, _ := ocireg.NewRepository(a.GetContext(), "ghcr.io/component-model/ocm")
+ r, _ = r.Dup()
+ return r
+}
+
+func (a *ComponentVersionAccess) GetDescriptor() *compdesc.ComponentDescriptor {
+ return &compdesc.ComponentDescriptor{
+ Metadata: compdesc.Metadata{},
+ ComponentSpec: compdesc.ComponentSpec{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "",
+ Version: "",
+ Labels: nil,
+ Provider: metav1.Provider{},
+ CreationTime: nil,
+ },
+ RepositoryContexts: nil,
+ Sources: nil,
+ References: nil,
+ Resources: nil,
+ },
+ Signatures: nil,
+ NestedDigests: nil,
+ }
+}
+
+func (a *ComponentVersionAccess) GetProvider() *compdesc.Provider {
+ return &a.GetDescriptor().Provider
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+type ResourceAccess ocm.ResourceMeta
+
+var _ ocm.ResourceAccess = (*ResourceAccess)(nil)
+
+func (r *ResourceAccess) Meta() *ocm.ResourceMeta {
+ return (*ocm.ResourceMeta)(r)
+}
+
+func (r ResourceAccess) GetComponentVersion() (internal.ComponentVersionAccess, error) {
+ // TODO implement me
+ panic("implement me")
+}
+
+func (r ResourceAccess) GetOCMContext() internal.Context {
+ // TODO implement me
+ panic("implement me")
+}
+
+func (r ResourceAccess) ReferenceHint() string {
+ // TODO implement me
+ panic("implement me")
+}
+
+func (r ResourceAccess) Access() (internal.AccessSpec, error) {
+ // TODO implement me
+ panic("implement me")
+}
+
+func (r ResourceAccess) AccessMethod() (internal.AccessMethod, error) {
+ // TODO implement me
+ panic("implement me")
+}
+
+func (r ResourceAccess) GlobalAccess() internal.AccessSpec {
+ // TODO implement me
+ panic("implement me")
+}
+
+func (r ResourceAccess) BlobAccess() (blobaccess.BlobAccess, error) {
+ // TODO implement me
+ panic("implement me")
+}
diff --git a/api/ocm/tools/transfer/transferhandler/plugin/options.go b/api/ocm/tools/transfer/transferhandler/plugin/options.go
new file mode 100644
index 0000000000..3960a303ce
--- /dev/null
+++ b/api/ocm/tools/transfer/transferhandler/plugin/options.go
@@ -0,0 +1,174 @@
+package plugin
+
+import (
+ "encoding/json"
+
+ "github.com/mandelsoft/goutils/errors"
+
+ "ocm.software/ocm/api/ocm/tools/transfer/transferhandler"
+ "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard"
+ "ocm.software/ocm/api/utils/runtime"
+)
+
+func init() {
+ transferhandler.RegisterHandler(100, &TransferOptionsCreator{})
+}
+
+type Options struct {
+ standard.Options
+ plugin string
+ handler string
+ config interface{}
+}
+
+var (
+ _ transferhandler.TransferOption = (*Options)(nil)
+ _ transferhandler.ConfigOptionConsumer = (*Options)(nil)
+
+ _ PluginNameOption = (*Options)(nil)
+ _ TransferHandlerOption = (*Options)(nil)
+ _ TransferHandlerConfigOption = (*Options)(nil)
+)
+
+type TransferOptionsCreator = transferhandler.SpecializedOptionsCreator[*Options, Options]
+
+func (o *Options) NewTransferHandler() (transferhandler.TransferHandler, error) {
+ return New(o)
+}
+
+func (o *Options) ApplyTransferOption(target transferhandler.TransferOptions) error {
+ if o.plugin != "" {
+ if opts, ok := target.(PluginNameOption); ok {
+ opts.SetPluginName(o.plugin)
+ }
+ }
+ if o.plugin != "" {
+ if opts, ok := target.(TransferHandlerOption); ok {
+ opts.SetTransferHandler(o.handler)
+ }
+ }
+ if o.config != nil {
+ if opts, ok := target.(TransferHandlerConfigOption); ok {
+ opts.SetTransferHandlerConfig(o.config)
+ }
+ }
+ return o.Options.ApplyTransferOption(target)
+}
+
+func (o *Options) SetPluginName(name string) {
+ o.plugin = name
+}
+
+func (o *Options) GetPluginName() string {
+ return o.plugin
+}
+
+func (o *Options) SetTransferHandler(name string) {
+ o.handler = name
+}
+
+func (o *Options) GetTransferHandler() string {
+ return o.handler
+}
+
+func (o *Options) SetTransferHandlerConfig(cfg interface{}) {
+ o.config = cfg
+}
+
+func (o *Options) GetTransferHandlerConfig() interface{} {
+ return o.config
+}
+
+func (o *Options) SetConfig(bytes []byte) {
+ c, err := runtime.ToJSON(bytes)
+ if err != nil {
+ c, _ = json.Marshal(string(bytes)) //nolint:errchkjson // yes
+ }
+ o.config = c
+}
+
+func (o *Options) GetConfig() []byte {
+ c, _ := runtime.ToJSON(o.config)
+ return c
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+type PluginNameOption interface {
+ SetPluginName(name string)
+ GetPluginName() string
+}
+
+type pluginNameOption struct {
+ TransferOptionsCreator
+ name string
+}
+
+func (o *pluginNameOption) ApplyTransferOption(to transferhandler.TransferOptions) error {
+ if eff, ok := to.(PluginNameOption); ok {
+ eff.SetPluginName(o.name)
+ return nil
+ } else {
+ return errors.ErrNotSupported(transferhandler.KIND_TRANSFEROPTION, "pluginName")
+ }
+}
+
+func Plugin(name string) transferhandler.TransferOption {
+ return &pluginNameOption{
+ name: name,
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+type TransferHandlerOption interface {
+ SetTransferHandler(name string)
+ GetTransferHandler() string
+}
+
+type transferHandlerOption struct {
+ TransferOptionsCreator
+ name string
+}
+
+func (o *transferHandlerOption) ApplyTransferOption(to transferhandler.TransferOptions) error {
+ if eff, ok := to.(TransferHandlerOption); ok {
+ eff.SetTransferHandler(o.name)
+ return nil
+ } else {
+ return errors.ErrNotSupported(transferhandler.KIND_TRANSFEROPTION, "transferHandler")
+ }
+}
+
+func TransferHandler(name string) transferhandler.TransferOption {
+ return &transferHandlerOption{
+ name: name,
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+type TransferHandlerConfigOption interface {
+ SetTransferHandlerConfig(cf interface{})
+ GetTransferHandlerConfig() interface{}
+}
+
+type transferHandlerConfigOption struct {
+ TransferOptionsCreator
+ config interface{}
+}
+
+func (o *transferHandlerConfigOption) ApplyTransferOption(to transferhandler.TransferOptions) error {
+ if eff, ok := to.(TransferHandlerConfigOption); ok {
+ eff.SetTransferHandlerConfig(o.config)
+ return nil
+ } else {
+ return errors.ErrNotSupported(transferhandler.KIND_TRANSFEROPTION, "transferHandlerConfig")
+ }
+}
+
+func TransferHandlerConfig(cfg interface{}) transferhandler.TransferOption {
+ return &transferHandlerConfigOption{
+ config: cfg,
+ }
+}
diff --git a/api/ocm/tools/transfer/transferhandler/plugin/registration.go b/api/ocm/tools/transfer/transferhandler/plugin/registration.go
new file mode 100644
index 0000000000..8cadcbeb63
--- /dev/null
+++ b/api/ocm/tools/transfer/transferhandler/plugin/registration.go
@@ -0,0 +1,115 @@
+package plugin
+
+import (
+ "fmt"
+ "slices"
+
+ "github.com/mandelsoft/goutils/errors"
+
+ "ocm.software/ocm/api/ocm"
+ "ocm.software/ocm/api/ocm/cpi"
+ "ocm.software/ocm/api/ocm/extensions/attrs/plugincacheattr"
+ "ocm.software/ocm/api/ocm/plugin"
+ "ocm.software/ocm/api/ocm/tools/transfer/transferhandler"
+ "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard"
+ "ocm.software/ocm/api/utils/registrations"
+)
+
+type Config = interface{}
+
+func init() {
+ transferhandler.RegisterHandlerRegistrationHandler("plugin", &RegistrationHandler{})
+}
+
+type RegistrationHandler struct{}
+
+var _ transferhandler.ByNameCreationHandler = (*RegistrationHandler)(nil)
+
+func (r *RegistrationHandler) ByName(ctx ocm.Context, handler string, opts ...transferhandler.TransferOption) (bool, transferhandler.TransferHandler, error) {
+ path := cpi.NewNamePath(handler)
+
+ if len(path) < 1 || len(path) > 2 {
+ return true, nil, fmt.Errorf("plugin handler name must be of the form [/]")
+ }
+
+ name := ""
+ if len(path) > 1 {
+ name = path[1]
+ }
+
+ h, err := CreateTransferHandler(ctx, path[0], name, opts...)
+ return true, h, err
+}
+
+func CreateTransferHandler(ctx ocm.Context, pname, name string, opts ...transferhandler.TransferOption) (transferhandler.TransferHandler, error) {
+ options := &Options{}
+ err := transferhandler.ApplyOptions(options, append(slices.Clone(opts), Plugin(pname))...)
+ if err != nil {
+ return nil, err
+ }
+
+ if options.plugin != "" && options.plugin != pname {
+ return nil, fmt.Errorf("plugin option not possible for path-based transferhandler creation")
+ }
+ if options.handler != "" && options.handler != name {
+ return nil, fmt.Errorf("plugin transfer handler option nor possible for path-based transferhandler creation")
+ }
+
+ set := plugincacheattr.Get(ctx)
+ if set == nil {
+ return nil, errors.ErrUnknown(plugin.KIND_PLUGIN, pname)
+ }
+
+ p := set.Get(pname)
+ if p == nil {
+ return nil, errors.ErrUnknown(plugin.KIND_PLUGIN, pname)
+ }
+ if name == "" {
+ names := p.GetTransferHandlerNames()
+ if len(names) == 0 {
+ return nil, fmt.Errorf("plugin %q does not provide transfer handlers", pname)
+ }
+ if len(names) != 1 {
+ return nil, fmt.Errorf("plugin %q provides more than one transfer handler", pname)
+ }
+ name = names[0]
+ }
+ d := p.GetTransferHandler(name)
+ if d == nil {
+ return nil, errors.ErrNotFound(plugin.KIND_TRANSFERHANDLER, name, pname)
+ }
+ TransferHandler(name).ApplyTransferOption(options)
+
+ return &Handler{
+ Handler: *standard.NewDefaultHandler(&options.Options),
+ opts: options,
+ plugin: p,
+ desc: d,
+ }, nil
+}
+
+func (r *RegistrationHandler) GetHandlers(target *transferhandler.Target) registrations.HandlerInfos {
+ infos := registrations.NewNodeHandlerInfo("transfer handlers provided by plugins",
+ "sub namespace of the form <plugin name>/<handler>")
+
+ set := plugincacheattr.Get(target.Context)
+ if set == nil {
+ return infos
+ }
+
+ for _, name := range set.PluginNames() {
+ p := set.Get(name)
+ if !p.IsValid() {
+ continue
+ }
+ for _, d := range set.Get(name).GetDescriptor().Downloaders {
+ i := registrations.HandlerInfo{
+ Name: name + "/" + d.GetName(),
+ ShortDesc: "",
+ Description: d.GetDescription(),
+ }
+ infos = append(infos, i)
+ }
+ }
+ return infos
+}
diff --git a/api/ocm/tools/transfer/transferhandler/plugin/suite_test.go b/api/ocm/tools/transfer/transferhandler/plugin/suite_test.go
new file mode 100644
index 0000000000..b8526b774f
--- /dev/null
+++ b/api/ocm/tools/transfer/transferhandler/plugin/suite_test.go
@@ -0,0 +1,13 @@
+package plugin_test
+
+import (
+ "testing"
+
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+)
+
+func TestConfig(t *testing.T) {
+ RegisterFailHandler(Fail)
+ RunSpecs(t, "transfer handler plugin Test Suite")
+}
diff --git a/api/ocm/tools/transfer/transferhandler/plugin/testdata/config b/api/ocm/tools/transfer/transferhandler/plugin/testdata/config
new file mode 100644
index 0000000000..338b2bcaab
--- /dev/null
+++ b/api/ocm/tools/transfer/transferhandler/plugin/testdata/config
@@ -0,0 +1,5 @@
+
+transferRepositories:
+ types:
+ ociArtifact:
+ - ghcr.io
\ No newline at end of file
diff --git a/api/ocm/tools/transfer/transferhandler/plugin/testdata/plugins/transferplugin b/api/ocm/tools/transfer/transferhandler/plugin/testdata/plugins/transferplugin
new file mode 100755
index 0000000000..6b442407f6
--- /dev/null
+++ b/api/ocm/tools/transfer/transferhandler/plugin/testdata/plugins/transferplugin
@@ -0,0 +1,2 @@
+#!/bin/bash
+go run ../../../../../../cmds/transferplugin/main.go "$@"
\ No newline at end of file
diff --git a/api/ocm/tools/transfer/transferhandler/plugin/testdata/transferconfig b/api/ocm/tools/transfer/transferhandler/plugin/testdata/transferconfig
new file mode 100644
index 0000000000..8be5ca90aa
--- /dev/null
+++ b/api/ocm/tools/transfer/transferhandler/plugin/testdata/transferconfig
@@ -0,0 +1,5 @@
+
+transferRepositories:
+ types:
+ ociArtifact:
+ - source.alias
\ No newline at end of file
diff --git a/api/ocm/tools/transfer/transferhandler/plugin/transfer_test.go b/api/ocm/tools/transfer/transferhandler/plugin/transfer_test.go
new file mode 100644
index 0000000000..4ddebc15c1
--- /dev/null
+++ b/api/ocm/tools/transfer/transferhandler/plugin/transfer_test.go
@@ -0,0 +1,156 @@
+package plugin_test
+
+import (
+ "os"
+
+ . "github.com/mandelsoft/goutils/testutils"
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+ . "ocm.software/ocm/api/helper/builder"
+ "ocm.software/ocm/api/ocm/extensions/attrs/plugincacheattr"
+ . "ocm.software/ocm/api/ocm/plugin/testutils"
+ . "ocm.software/ocm/cmds/ocm/testhelper"
+
+ "ocm.software/ocm/api/oci"
+ . "ocm.software/ocm/api/oci/testhelper"
+ metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1"
+ "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob"
+ "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact"
+ resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes"
+ "ocm.software/ocm/api/ocm/extensions/repositories/ctf"
+ "ocm.software/ocm/api/ocm/tools/transfer"
+ "ocm.software/ocm/api/ocm/tools/transfer/transferhandler"
+ "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard"
+ "ocm.software/ocm/api/utils"
+ "ocm.software/ocm/api/utils/accessio"
+ "ocm.software/ocm/api/utils/accessobj"
+ common "ocm.software/ocm/api/utils/misc"
+)
+
+const OCIPATH1 = "oci1"
+const OCIPATH2 = "oci2"
+
+const OCIHOST1 = "source"
+const OCIHOST2 = "target"
+
+const COMPONENT = "acme.org/component"
+const VERSION = "1.0.0"
+const PROVIDER = "acme.org"
+
+var _ = Describe("transport with plugin based transfer handler", func() {
+ var env *Builder
+ var plugins TempPluginDir
+
+ BeforeEach(func() {
+ env = NewBuilder(TestData())
+ plugins = Must(ConfigureTestPlugins(env, "testdata/plugins"))
+
+ env.OCICommonTransport(OCIPATH1, accessio.FormatDirectory, func() {
+ OCIManifest1(env)
+ })
+
+ env.OCICommonTransport(OCIPATH2, accessio.FormatDirectory, func() {
+ OCIManifest1(env)
+ })
+
+ FakeOCIRepo(env, OCIPATH1, OCIHOST1)
+ FakeOCIRepo(env, OCIPATH2, OCIHOST2)
+
+ env.OCMCommonTransport(OCIPATH1, accessio.FormatDirectory, func() {
+ env.Component(COMPONENT, func() {
+ env.Version(VERSION, func() {
+ env.Provider(PROVIDER)
+ env.Resource("image", "", resourcetypes.OCI_IMAGE, metav1.LocalRelation, func() {
+ env.Access(
+ ociartifact.New(oci.StandardOCIRef(OCIHOST1+".alias", OCINAMESPACE, OCIVERSION)),
+ )
+ })
+ })
+ })
+ })
+ })
+
+ AfterEach(func() {
+ plugins.Cleanup()
+ env.Cleanup()
+ })
+
+ It("loads plugin", func() {
+ registry := plugincacheattr.Get(env)
+ // Expect(registration.RegisterExtensions(env)).To(Succeed())
+ p := registry.Get(PLUGIN)
+ Expect(p).NotTo(BeNil())
+ Expect(p.Error()).To(Equal(""))
+ })
+
+ DescribeTable("transfers per handler", func(cfgfile string) {
+ config := Must(os.ReadFile("testdata/" + cfgfile))
+ p, buf := common.NewBufferedPrinter()
+ topts := append([]transferhandler.TransferOption{
+ transfer.WithPrinter(p),
+ transferhandler.WithConfig(config),
+ })
+
+ h := Must(transferhandler.For(env).ByName(env, "plugin/transferplugin/demo", topts...))
+
+ src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, OCIPATH1, 0, env))
+ defer Close(src, "src")
+
+ cv := Must(src.LookupComponentVersion(COMPONENT, VERSION))
+ defer Close(cv, "cv")
+
+ tgt := Must(ctf.Open(env, accessobj.ACC_WRITABLE, OCIPATH2, 0, env))
+ ctgt := accessio.OnceCloser(tgt)
+ defer Close(ctgt, "tgt")
+
+ MustBeSuccessful(transfer.TransferWithHandler(p, cv, tgt, h))
+
+ options := &standard.Options{}
+ transferhandler.ApplyOptions(options, topts...)
+
+ out := `
+ transferring version "acme.org/component:1.0.0"...
+ ...resource 0 image\[ociImage\]\(ocm/value:v2.0\)...
+ ...adding component version...
+`
+ if cfgfile == "config" {
+ out = `
+ transferring version "acme.org/component:1.0.0"...
+ ...adding component version...
+`
+ }
+ Expect(string(buf.Bytes())).To(StringMatchTrimmedWithContext(utils.Crop(out, 2)))
+ MustBeSuccessful(ctgt.Close())
+
+ tgt = Must(ctf.Open(env, accessobj.ACC_READONLY, OCIPATH2, 0, env))
+ ctgt = accessio.OnceCloser(tgt)
+ defer Close(ctgt, "tgt2")
+
+ tcv := Must(tgt.LookupComponentVersion(COMPONENT, VERSION))
+ ctcv := accessio.OnceCloser(tcv)
+ defer Close(ctcv, "tcv")
+
+ r := Must(tcv.GetResourceByIndex(0))
+ acc := Must(r.Access())
+
+ atype := localblob.Type
+ if cfgfile == "config" {
+ atype = ociartifact.Type
+ }
+ Expect(acc.GetKind()).To(Equal(atype))
+
+ info := acc.Info(env.OCMContext())
+
+ infotxt := "sha256:" + H_OCIARCHMANIFEST1
+ if cfgfile == "config" {
+ infotxt = "ocm/value:v2.0"
+ }
+ Expect(info.Info).To(Equal(infotxt))
+
+ MustBeSuccessful(ctcv.Close())
+ MustBeSuccessful(ctgt.Close())
+ },
+ Entry("with matching oci host", "transferconfig"),
+ Entry("without matching oci host", "config"),
+ )
+})
diff --git a/api/ocm/tools/transfer/transferhandler/registration.go b/api/ocm/tools/transfer/transferhandler/registration.go
new file mode 100644
index 0000000000..feb18737f4
--- /dev/null
+++ b/api/ocm/tools/transfer/transferhandler/registration.go
@@ -0,0 +1,133 @@
+package transferhandler
+
+import (
+ "sort"
+
+ "github.com/mandelsoft/goutils/general"
+
+ "ocm.software/ocm/api/datacontext"
+ ocm "ocm.software/ocm/api/ocm/cpi"
+ "ocm.software/ocm/api/utils/listformat"
+ "ocm.software/ocm/api/utils/registrations"
+)
+
+const ATTR_TRANSFER_HANDLERS = "ocm.software/ocm/api/ocm/tools/transfer/transferhandlers"
+
+func For(ctx ocm.ContextProvider) Registry {
+ if ctx == nil {
+ return DefaultRegistry
+ }
+ return ctx.OCMContext().GetAttributes().GetOrCreateAttribute(ATTR_TRANSFER_HANDLERS, create).(Registry)
+}
+
+func create(datacontext.Context) interface{} {
+ return NewRegistry(DefaultRegistry)
+}
+
+func SetFor(ctx datacontext.Context, registry Registry) {
+ ctx.GetAttributes().SetAttribute(ATTR_TRANSFER_HANDLERS, registry)
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// The creation handler is implemented by the RegistrationHandlerRegistry,
+// but inśtead of executing a handler registration, like for the downloaders,
+// the created handler is just returned,
+
+type ByNameCreationHandler interface {
+ ByName(ctx ocm.Context, path string, olist ...TransferOption) (bool, TransferHandler, error)
+ GetHandlers(t *Target) registrations.HandlerInfos
+}
+
+type HandlerInfos = registrations.HandlerInfos
+
+// byNameCreationHandler wraps the handler interface
+// into the implementation type.
+type byNameCreationHandler struct {
+ handler ByNameCreationHandler
+}
+
+func (b byNameCreationHandler) GetHandlers(t *Target) HandlerInfos {
+ return b.handler.GetHandlers(t)
+}
+
+func (b byNameCreationHandler) RegisterByName(path string, target *Target, _ registrations.HandlerConfig, opts ...TransferOption) (bool, error) {
+ ok, h, err := b.handler.ByName(target.Context, path, opts...)
+ target.Handler = h
+ return ok, err
+}
+
+var _ registrations.HandlerRegistrationHandler[*Target, TransferOption] = (*byNameCreationHandler)(nil)
+
+////////////////////////////////////////////////////////////////////////////////
+
+type Target struct {
+ Context ocm.Context
+ Handler TransferHandler
+}
+
+func NewTarget(ctx ocm.ContextProvider) *Target {
+ return &Target{Context: ctx.OCMContext()}
+}
+
+func RegisterHandlerRegistrationHandler(path string, handler ByNameCreationHandler) {
+ DefaultRegistry.RegisterRegistrationHandler(path, handler)
+}
+
+func CreateByName(ctx ocm.ContextProvider, name string, opts ...TransferOption) (TransferHandler, error) {
+ return For(ctx).ByName(ctx, name, opts...)
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+var DefaultRegistry = NewRegistry()
+
+type Registry interface {
+ RegisterRegistrationHandler(path string, handler ByNameCreationHandler)
+
+ ByName(ctx ocm.ContextProvider, name string, opts ...TransferOption) (TransferHandler, error)
+
+ Copy() Registry
+ AsHandlerRegistrationRegistry() registrations.HandlerRegistrationRegistry[*Target, TransferOption]
+}
+
+type _registry struct {
+ registry registrations.HandlerRegistrationRegistry[*Target, TransferOption]
+ base Registry
+}
+
+func NewRegistry(base ...Registry) Registry {
+ b := general.Optional(base...)
+ return &_registry{registrations.NewHandlerRegistrationRegistry[*Target, TransferOption](asHandlerRegistrationRegistry(b)), b}
+}
+
+func (r *_registry) RegisterRegistrationHandler(path string, handler ByNameCreationHandler) {
+ r.registry.RegisterRegistrationHandler(path, &byNameCreationHandler{handler})
+}
+
+func (r *_registry) ByName(ctx ocm.ContextProvider, name string, opts ...TransferOption) (TransferHandler, error) {
+ target := NewTarget(ctx.OCMContext())
+ _, err := r.registry.RegisterByName(name, target, nil, opts...)
+ return target.Handler, err
+}
+
+func (r *_registry) Copy() Registry {
+ return NewRegistry(r.base)
+}
+
+func (r *_registry) AsHandlerRegistrationRegistry() registrations.HandlerRegistrationRegistry[*Target, TransferOption] {
+ return r.registry
+}
+
+func asHandlerRegistrationRegistry(r Registry) registrations.HandlerRegistrationRegistry[*Target, TransferOption] {
+ if r == nil {
+ return nil
+ }
+ return r.AsHandlerRegistrationRegistry()
+}
+
+func Usage(ctx ocm.Context) string {
+ list := For(ctx).AsHandlerRegistrationRegistry().GetHandlers(NewTarget(ctx))
+ sort.Sort(list)
+ return `The following transfer handler names are supported:
+` + listformat.FormatListElements("", list)
+}
diff --git a/api/ocm/tools/transfer/transferhandler/registration_test.go b/api/ocm/tools/transfer/transferhandler/registration_test.go
new file mode 100644
index 0000000000..88896a9914
--- /dev/null
+++ b/api/ocm/tools/transfer/transferhandler/registration_test.go
@@ -0,0 +1,39 @@
+package transferhandler_test
+
+import (
+ "reflect"
+
+ . "github.com/mandelsoft/goutils/testutils"
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+ "ocm.software/ocm/api/ocm/tools/transfer/transferhandler"
+
+ _ "ocm.software/ocm/api/ocm/tools/transfer"
+
+ "github.com/mandelsoft/goutils/errors"
+ "github.com/mandelsoft/goutils/generics"
+
+ "ocm.software/ocm/api/datacontext"
+ "ocm.software/ocm/api/ocm"
+ "ocm.software/ocm/api/ocm/plugin"
+ "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/spiff"
+ "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard"
+)
+
+var _ = Describe("Registration Test Environment", func() {
+ ctx := ocm.New(datacontext.MODE_EXTENDED)
+
+ It("standard", func() {
+ h := Must(transferhandler.For(ctx).ByName(ctx, "ocm/standard"))
+ Expect(reflect.TypeOf(h)).To(Equal(generics.TypeOf[*standard.Handler]()))
+ })
+
+ It("spiff", func() {
+ h := Must(transferhandler.For(ctx).ByName(ctx, "ocm/spiff"))
+ Expect(reflect.TypeOf(h)).To(Equal(generics.TypeOf[*spiff.Handler]()))
+ })
+
+ It("plugin", func() {
+ ExpectError(transferhandler.For(ctx).ByName(ctx, "plugin/p/h")).To(MatchError(errors.ErrUnknown(plugin.KIND_PLUGIN, "p")))
+ })
+})
diff --git a/api/ocm/tools/transfer/transferhandler/setup.go b/api/ocm/tools/transfer/transferhandler/setup.go
new file mode 100644
index 0000000000..15a3c1e959
--- /dev/null
+++ b/api/ocm/tools/transfer/transferhandler/setup.go
@@ -0,0 +1,27 @@
+package transferhandler
+
+import (
+ "ocm.software/ocm/api/datacontext"
+ "ocm.software/ocm/api/ocm/cpi"
+)
+
+func init() {
+ datacontext.RegisterSetupHandler(datacontext.SetupHandlerFunction(setupContext))
+}
+
+func setupContext(mode datacontext.BuilderMode, ctx datacontext.Context) {
+ if octx, ok := ctx.(cpi.Context); ok {
+ switch mode {
+ case datacontext.MODE_SHARED:
+ fallthrough
+ case datacontext.MODE_DEFAULTED:
+ // do nothing, fallback to the default attribute lookup
+ case datacontext.MODE_EXTENDED:
+ SetFor(octx, NewRegistry(DefaultRegistry))
+ case datacontext.MODE_CONFIGURED:
+ SetFor(octx, DefaultRegistry.Copy())
+ case datacontext.MODE_INITIAL:
+ SetFor(octx, NewRegistry())
+ }
+ }
+}
diff --git a/api/ocm/tools/transfer/transferhandler/spiff/handler.go b/api/ocm/tools/transfer/transferhandler/spiff/handler.go
index 4044ffdf95..040572ff62 100644
--- a/api/ocm/tools/transfer/transferhandler/spiff/handler.go
+++ b/api/ocm/tools/transfer/transferhandler/spiff/handler.go
@@ -41,6 +41,10 @@ func New(opts ...transferhandler.TransferOption) (transferhandler.TransferHandle
}, nil
}
+func (h *Handler) GetScript() []byte {
+ return h.opts.GetScript()
+}
+
// TODO: handle update and overwrite per script
func (h *Handler) UpdateVersion(src ocm.ComponentVersionAccess, tgt ocm.ComponentVersionAccess) (bool, error) {
@@ -50,7 +54,7 @@ func (h *Handler) UpdateVersion(src ocm.ComponentVersionAccess, tgt ocm.Componen
if h.opts.GetScript() == nil {
return false, nil
}
- binding := h.getBinding(src, nil, nil, nil, nil)
+ binding := h.getBinding(src, nil, nil, nil, tgt.Repository())
return h.EvalBool("update", binding, "process")
}
@@ -61,7 +65,7 @@ func (h *Handler) EnforceTransport(src ocm.ComponentVersionAccess, tgt ocm.Compo
if h.opts.GetScript() == nil {
return false, nil
}
- binding := h.getBinding(src, nil, nil, nil, nil)
+ binding := h.getBinding(src, nil, nil, nil, tgt.Repository())
return h.EvalBool("enforceTransport", binding, "process")
}
@@ -72,7 +76,7 @@ func (h *Handler) OverwriteVersion(src ocm.ComponentVersionAccess, tgt ocm.Compo
if h.opts.GetScript() == nil {
return false, nil
}
- binding := h.getBinding(src, nil, nil, nil, nil)
+ binding := h.getBinding(src, nil, nil, nil, tgt.Repository())
return h.EvalBool("overwrite", binding, "process")
}
diff --git a/api/ocm/tools/transfer/transferhandler/spiff/handler_test.go b/api/ocm/tools/transfer/transferhandler/spiff/handler_test.go
index 72e2e5aa94..c371b2ab54 100644
--- a/api/ocm/tools/transfer/transferhandler/spiff/handler_test.go
+++ b/api/ocm/tools/transfer/transferhandler/spiff/handler_test.go
@@ -3,13 +3,17 @@ package spiff_test
import (
"encoding/json"
"fmt"
+ "reflect"
+ "github.com/mandelsoft/goutils/generics"
+ . "github.com/mandelsoft/goutils/testutils"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
+ "ocm.software/ocm/api/datacontext"
. "ocm.software/ocm/api/helper/builder"
. "ocm.software/ocm/api/oci/testhelper"
-
- "github.com/mandelsoft/goutils/testutils"
+ "ocm.software/ocm/api/ocm"
+ "ocm.software/ocm/api/ocm/tools/transfer/transferhandler"
"ocm.software/ocm/api/oci"
"ocm.software/ocm/api/oci/artdesc"
@@ -18,7 +22,7 @@ import (
"ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact"
resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes"
"ocm.software/ocm/api/ocm/extensions/repositories/ctf"
- ocmutils "ocm.software/ocm/api/ocm/ocmutils"
+ "ocm.software/ocm/api/ocm/ocmutils"
"ocm.software/ocm/api/ocm/tools/transfer"
"ocm.software/ocm/api/ocm/tools/transfer/transferhandler/spiff"
"ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard"
@@ -197,7 +201,7 @@ process: (( (*(rules[mode] || rules.default)).process ))
Expect(err).To(Succeed())
fmt.Printf("%s\n", string(data))
hash := HashManifest2(artifactset.DefaultArtifactSetDescriptorFileName)
- Expect(string(data)).To(testutils.StringEqualWithContext("{\"localReference\":\"" + hash + "\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\"" + OCINAMESPACE2 + ":" + OCIVERSION + "\",\"type\":\"localBlob\"}"))
+ Expect(string(data)).To(StringEqualWithContext("{\"localReference\":\"" + hash + "\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\"" + OCINAMESPACE2 + ":" + OCIVERSION + "\",\"type\":\"localBlob\"}"))
data, err = json.Marshal(comp.GetDescriptor().Resources[1].Access)
Expect(err).To(Succeed())
@@ -295,4 +299,15 @@ process: (( (*(rules[mode] || rules.default)).process ))
Expect(err).To(Succeed())
})
})
+
+ Context("registrations", func() {
+ ctx := ocm.New(datacontext.MODE_EXTENDED)
+
+ It("finds plugin handler by name and config", func() {
+ h := Must(transferhandler.For(ctx).ByName(ctx, "ocm/spiff", transferhandler.WithConfig([]byte(script1))))
+ Expect(reflect.TypeOf(h)).To(Equal(generics.TypeOf[*spiff.Handler]()))
+ Expect(string(h.(*spiff.Handler).GetScript())).To(Equal(script1))
+ })
+ })
+
})
diff --git a/api/ocm/tools/transfer/transferhandler/spiff/options.go b/api/ocm/tools/transfer/transferhandler/spiff/options.go
index 68ba1c2aa4..d05d65a9b3 100644
--- a/api/ocm/tools/transfer/transferhandler/spiff/options.go
+++ b/api/ocm/tools/transfer/transferhandler/spiff/options.go
@@ -29,10 +29,6 @@ var (
type TransferOptionsCreator = transferhandler.SpecializedOptionsCreator[*Options, Options]
-func (o *Options) NewOptions() transferhandler.TransferHandlerOptions {
- return &Options{}
-}
-
func (o *Options) NewTransferHandler() (transferhandler.TransferHandler, error) {
return New(o)
}
@@ -121,6 +117,14 @@ func ScriptByFile(path string, fss ...vfs.FileSystem) transferhandler.TransferOp
}
}
+func (o *Options) SetConfig(bytes []byte) {
+ o.script = bytes
+}
+
+func (o *Options) GetConfig() []byte {
+ return o.script
+}
+
///////////////////////////////////////////////////////////////////////////////
type ScriptFilesystemOption interface {
diff --git a/api/ocm/tools/transfer/transferhandler/spiff/registration.go b/api/ocm/tools/transfer/transferhandler/spiff/registration.go
new file mode 100644
index 0000000000..332cbcbef5
--- /dev/null
+++ b/api/ocm/tools/transfer/transferhandler/spiff/registration.go
@@ -0,0 +1,33 @@
+package spiff
+
+import (
+ "fmt"
+
+ "ocm.software/ocm/api/ocm"
+ "ocm.software/ocm/api/ocm/tools/transfer/transferhandler"
+ "ocm.software/ocm/api/utils/registrations"
+)
+
+func init() {
+ transferhandler.RegisterHandlerRegistrationHandler("ocm/spiff", &RegistrationHandler{})
+}
+
+type RegistrationHandler struct{}
+
+var _ transferhandler.ByNameCreationHandler = (*RegistrationHandler)(nil)
+
+func (r *RegistrationHandler) ByName(ctx ocm.Context, path string, olist ...transferhandler.TransferOption) (bool, transferhandler.TransferHandler, error) {
+ if path != "" {
+ return true, nil, fmt.Errorf("invalid standard handler %q", path)
+ }
+
+ h, err := New(olist...)
+ return true, h, err
+}
+
+func (r *RegistrationHandler) GetHandlers(target *transferhandler.Target) registrations.HandlerInfos {
+ return registrations.NewLeafHandlerInfo("spiff transfer handler", `
+The spiff transfer handler works on the standard transfer options
+extended by dynamic programming based on spiff++.`,
+ )
+}
diff --git a/api/ocm/tools/transfer/transferhandler/standard/options.go b/api/ocm/tools/transfer/transferhandler/standard/options.go
index bfc0524753..25abc503b9 100644
--- a/api/ocm/tools/transfer/transferhandler/standard/options.go
+++ b/api/ocm/tools/transfer/transferhandler/standard/options.go
@@ -52,10 +52,6 @@ var (
type TransferOptionsCreator = transferhandler.SpecializedOptionsCreator[*Options, Options]
-func (o *Options) NewOptions() transferhandler.TransferHandlerOptions {
- return &Options{}
-}
-
func (o *Options) NewTransferHandler() (transferhandler.TransferHandler, error) {
return New(o)
}
@@ -141,6 +137,10 @@ func (o *Options) IsTransportEnforced() bool {
return optionutils.AsBool(o.enforceTransport)
}
+func (o *Options) GetEnforceTransport() *bool {
+ return o.enforceTransport
+}
+
func (o *Options) SetOverwrite(overwrite bool) {
o.overwrite = &overwrite
}
@@ -149,6 +149,10 @@ func (o *Options) IsOverwrite() bool {
return optionutils.AsBool(o.overwrite)
}
+func (o *Options) GetOverwrite() *bool {
+ return o.overwrite
+}
+
func (o *Options) SetSkipUpdate(skipupdate bool) {
o.skipUpdate = &skipupdate
}
@@ -157,6 +161,10 @@ func (o *Options) IsSkipUpdate() bool {
return optionutils.AsBool(o.skipUpdate)
}
+func (o *Options) GetSkipUpdate() *bool {
+ return o.skipUpdate
+}
+
func (o *Options) SetRecursive(recursive bool) {
o.recursive = &recursive
}
@@ -165,6 +173,10 @@ func (o *Options) IsRecursive() bool {
return optionutils.AsBool(o.recursive)
}
+func (o *Options) GetRecursive() *bool {
+ return o.recursive
+}
+
func (o *Options) SetResourcesByValue(resourcesByValue bool) {
o.resourcesByValue = &resourcesByValue
}
@@ -173,6 +185,10 @@ func (o *Options) IsResourcesByValue() bool {
return optionutils.AsBool(o.resourcesByValue)
}
+func (o *Options) GetResourcesByValue() *bool {
+ return o.resourcesByValue
+}
+
func (o *Options) SetLocalResourcesByValue(resourcesByValue bool) {
o.localByValue = &resourcesByValue
}
@@ -181,6 +197,10 @@ func (o *Options) IsLocalResourcesByValue() bool {
return optionutils.AsBool(o.localByValue)
}
+func (o *Options) GetLocalResourcesByValue() *bool {
+ return o.localByValue
+}
+
func (o *Options) SetSourcesByValue(sourcesByValue bool) {
o.sourcesByValue = &sourcesByValue
}
@@ -189,6 +209,10 @@ func (o *Options) IsSourcesByValue() bool {
return optionutils.AsBool(o.sourcesByValue)
}
+func (o *Options) GetSourcesByValue() *bool {
+ return o.sourcesByValue
+}
+
func (o *Options) SetKeepGlobalAccess(keepGlobalAccess bool) {
o.keepGlobalAccess = &keepGlobalAccess
}
@@ -197,6 +221,10 @@ func (o *Options) IsKeepGlobalAccess() bool {
return optionutils.AsBool(o.keepGlobalAccess)
}
+func (o *Options) GetKeepGlobalAccess() *bool {
+ return o.keepGlobalAccess
+}
+
func (o *Options) SetRetries(retries int) {
o.retries = &retries
}
@@ -224,6 +252,10 @@ func (o *Options) IsStopOnExistingVersion() bool {
return optionutils.AsBool(o.stopOnExisting)
}
+func (o *Options) GetStopOnExistingVersion() *bool {
+ return o.stopOnExisting
+}
+
func (o *Options) SetOmittedAccessTypes(list ...string) {
o.omitAccessTypes = set.New[string]()
for _, t := range list {
diff --git a/api/ocm/tools/transfer/transferhandler/standard/registration.go b/api/ocm/tools/transfer/transferhandler/standard/registration.go
new file mode 100644
index 0000000000..1081e38ac0
--- /dev/null
+++ b/api/ocm/tools/transfer/transferhandler/standard/registration.go
@@ -0,0 +1,32 @@
+package standard
+
+import (
+ "fmt"
+
+ "ocm.software/ocm/api/ocm"
+ "ocm.software/ocm/api/ocm/tools/transfer/transferhandler"
+ "ocm.software/ocm/api/utils/registrations"
+)
+
+func init() {
+ transferhandler.RegisterHandlerRegistrationHandler("ocm/standard", &RegistrationHandler{})
+}
+
+type RegistrationHandler struct{}
+
+var _ transferhandler.ByNameCreationHandler = (*RegistrationHandler)(nil)
+
+func (r *RegistrationHandler) ByName(ctx ocm.Context, path string, olist ...transferhandler.TransferOption) (bool, transferhandler.TransferHandler, error) {
+ if path != "" {
+ return true, nil, fmt.Errorf("invalid standard handler %q", path)
+ }
+
+ h, err := New(olist...)
+ return true, h, err
+}
+
+func (r *RegistrationHandler) GetHandlers(target *transferhandler.Target) registrations.HandlerInfos {
+ return registrations.NewLeafHandlerInfo("standard transfer handler", `
+The standard transfer handler works on the standard transfer options.`,
+ )
+}
diff --git a/api/ocm/tools/transfer/transferhandler/standard/transfer_test.go b/api/ocm/tools/transfer/transferhandler/standard/transfer_test.go
new file mode 100644
index 0000000000..ed86c529af
--- /dev/null
+++ b/api/ocm/tools/transfer/transferhandler/standard/transfer_test.go
@@ -0,0 +1,305 @@
+package standard_test
+
+import (
+ . "github.com/mandelsoft/goutils/testutils"
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+ . "ocm.software/ocm/api/helper/builder"
+ "ocm.software/ocm/api/oci"
+ "ocm.software/ocm/api/oci/artdesc"
+ "ocm.software/ocm/api/oci/extensions/repositories/artifactset"
+ ocictf "ocm.software/ocm/api/oci/extensions/repositories/ctf"
+ . "ocm.software/ocm/api/oci/testhelper"
+ "ocm.software/ocm/api/ocm"
+ metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1"
+ "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob"
+ "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact"
+ "ocm.software/ocm/api/ocm/extensions/accessmethods/relativeociref"
+ resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes"
+ "ocm.software/ocm/api/ocm/extensions/attrs/keepblobattr"
+ "ocm.software/ocm/api/ocm/extensions/blobhandler"
+ storagecontext "ocm.software/ocm/api/ocm/extensions/blobhandler/handlers/oci"
+ "ocm.software/ocm/api/ocm/extensions/blobhandler/handlers/oci/ocirepo"
+ "ocm.software/ocm/api/ocm/extensions/repositories/ctf"
+ "ocm.software/ocm/api/ocm/tools/transfer"
+ "ocm.software/ocm/api/ocm/tools/transfer/transferhandler"
+ "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard"
+ "ocm.software/ocm/api/utils"
+ "ocm.software/ocm/api/utils/accessio"
+ "ocm.software/ocm/api/utils/accessobj"
+ common "ocm.software/ocm/api/utils/misc"
+)
+
+const OCIHOST2 = "target"
+
+var _ = Describe("value transport with relative ocireg", func() {
+ var env *Builder
+
+ BeforeEach(func() {
+ env = NewBuilder()
+
+ env.RSAKeyPair(SIGNATURE)
+
+ env.OCICommonTransport(OCIPATH, accessio.FormatDirectory, func() {
+ OCIManifest1(env)
+ })
+
+ FakeOCIRepo(env, OCIPATH, OCIHOST)
+ FakeOCIRepo(env, ARCH, OCIHOST2)
+
+ env.OCMCommonTransport(OCIPATH, accessio.FormatDirectory, func() {
+ env.Component(COMPONENT, func() {
+ env.Version(VERSION, func() {
+ env.Provider(PROVIDER)
+ env.Resource("artifact", "", resourcetypes.OCI_IMAGE, metav1.LocalRelation, func() {
+ env.Access(
+ relativeociref.New(oci.RelativeOCIRef(OCINAMESPACE, OCIVERSION)),
+ )
+ })
+ })
+ })
+ })
+ })
+
+ AfterEach(func() {
+ env.Cleanup()
+ })
+
+ It("it should use additional resolver to resolve component ref", func() {
+ src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, OCIPATH, 0, env))
+ defer Close(src, "src")
+
+ cv := Must(src.LookupComponentVersion(COMPONENT, VERSION))
+ defer Close(cv, "cv")
+
+ r := Must(cv.GetResourceByIndex(0))
+ CheckBlob(r, D_OCIMANIFEST1, 628)
+ })
+
+ DescribeTable("transfers per value", func(keep bool, mod func(env *Builder), dig string, size int, opts ...transferhandler.TransferOption) {
+ env.OCMContext().BlobHandlers().Register(ocirepo.NewArtifactHandler(FakeOCIRegBaseFunction),
+ blobhandler.ForRepo(oci.CONTEXT_TYPE, ocictf.Type), blobhandler.ForMimeType(artdesc.ToContentMediaType(artdesc.MediaTypeImageManifest)))
+ keepblobattr.Set(env.OCMContext(), keep)
+
+ p, buf := common.NewBufferedPrinter()
+ topts := append([]transferhandler.TransferOption{
+ standard.ResourcesByValue(), transfer.WithPrinter(p),
+ }, opts...)
+
+ src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, OCIPATH, 0, env))
+ defer Close(src, "src")
+
+ cv := Must(src.LookupComponentVersion(COMPONENT, VERSION))
+ defer Close(cv, "cv")
+
+ mod(env)
+
+ tgt := Must(ctf.Open(env, accessobj.ACC_WRITABLE, ARCH, 0, env))
+ ctgt := accessio.OnceCloser(tgt)
+ defer Close(ctgt, "tgt")
+
+ MustBeSuccessful(transfer.Transfer(cv, tgt, topts...))
+
+ options := &standard.Options{}
+ transferhandler.ApplyOptions(options, topts...)
+
+ out := `
+ transferring version "github.com/mandelsoft/test:v1"...
+ ...resource 0 artifact\[ociImage\]\(ocm/value:v2.0\)...
+ ...adding component version...
+`
+ if options.IsOverwrite() {
+ out = `
+ transferring version "github.com/mandelsoft/test:v1"...
+ warning: version "github.com/mandelsoft/test:v1" already present, but differs because some artifact.*changed \(transport enforced by overwrite option\)
+ ...resource 0 artifact\[ociImage\]\(ocm/value:v2.0\).*
+ ...adding component version...
+`
+ }
+ Expect(string(buf.Bytes())).To(StringMatchTrimmedWithContext(utils.Crop(out, 2)))
+ MustBeSuccessful(ctgt.Close())
+
+ tgt = Must(ctf.Open(env, accessobj.ACC_READONLY, ARCH, 0, env))
+ ctgt = accessio.OnceCloser(tgt)
+ defer Close(ctgt, "tgt2")
+
+ tcv := Must(tgt.LookupComponentVersion(COMPONENT, VERSION))
+ ctcv := accessio.OnceCloser(tcv)
+ defer Close(ctcv, "tcv")
+
+ r := Must(tcv.GetResourceByIndex(0))
+ acc := Must(r.Access())
+
+ atype := ociartifact.Type
+ if keep {
+ atype = localblob.Type
+ }
+ Expect(acc.GetKind()).To(Equal(atype))
+
+ info := acc.Info(env.OCMContext())
+ if keep {
+ Expect(info.Info).To(Equal("sha256:" + H_OCIARCHMANIFEST1))
+
+ } else {
+ Expect(info.Host).To(Equal(OCIHOST2 + ".alias"))
+ Expect(info.Info).To(Equal("ocm/value:v2.0@sha256:" + D_OCIMANIFEST1))
+ }
+
+ CheckBlob(r, dig, size)
+
+ MustBeSuccessful(ctcv.Close())
+ MustBeSuccessful(ctgt.Close())
+
+ CheckAritifact(env, dig, size)
+
+ // re-transport
+ buf.Reset()
+ tgt = Must(ctf.Open(env, accessobj.ACC_WRITABLE, ARCH, 0, env))
+ ctgt = accessio.OnceCloser(tgt)
+ defer Close(ctgt, "tgt3")
+
+ MustBeSuccessful(transfer.Transfer(cv, tgt, topts...))
+
+ tcv = Must(tgt.LookupComponentVersion(COMPONENT, VERSION))
+ ctcv = accessio.OnceCloser(tcv)
+ defer Close(ctcv, "ctcv")
+
+ r = Must(tcv.GetResourceByIndex(0))
+ acc = Must(r.Access())
+ Expect(acc.GetKind()).To(Equal(atype))
+
+ info = acc.Info(env.OCMContext())
+ if keep {
+ Expect(info.Info).To(Equal("sha256:" + H_OCIARCHMANIFEST1))
+
+ } else {
+ Expect(info.Info).To(Equal("ocm/value:v2.0@sha256:" + D_OCIMANIFEST1))
+ }
+
+ MustBeSuccessful(ctcv.Close())
+ MustBeSuccessful(ctgt.Close())
+
+ CheckAritifact(env, dig, size)
+
+ },
+ Entry("empty target", false, EmptyTarget, D_OCIMANIFEST1, 628),
+ Entry("identical target", false, IdenticalTarget, D_OCIMANIFEST1, 628),
+ Entry("different target", false, DifferentTarget, D_OCIMANIFEST1, 628),
+ Entry("different CV", false, DifferentCV, D_OCIMANIFEST1, 628, standard.Overwrite()),
+ Entry("different namespace", false, DifferentNamespace, D_OCIMANIFEST1, 628, standard.Overwrite()),
+ Entry("different name", false, DifferentName, D_OCIMANIFEST1, 628, standard.Overwrite()),
+ Entry("keep, empty target", true, EmptyTarget, D_OCIMANIFEST1, 628),
+
+ Entry("keep, identical target", true, IdenticalTarget, D_OCIMANIFEST1, 628),
+ Entry("keep, different target", true, DifferentTarget, D_OCIMANIFEST1, 628),
+ Entry("keep, different CV", true, DifferentCV, D_OCIMANIFEST1, 628, standard.Overwrite()),
+ Entry("keep, different namespace", true, DifferentNamespace, D_OCIMANIFEST1, 628, standard.Overwrite()),
+ Entry("keep, different name", true, DifferentName, D_OCIMANIFEST1, 628, standard.Overwrite()),
+ )
+})
+
+func EmptyTarget(env *Builder) {
+ env.OCMCommonTransport(ARCH, accessio.FormatDirectory)
+}
+
+func IdenticalTarget(env *Builder) {
+ env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() {
+ OCIManifest1For(env, OCINAMESPACE, OCIVERSION)
+ })
+}
+
+func DifferentTarget(env *Builder) {
+ env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() {
+ OCIManifest2For(env, OCINAMESPACE, OCIVERSION)
+ })
+}
+
+func DifferentCV(env *Builder) {
+ env.OCICommonTransport(ARCH, accessio.FormatDirectory, func() {
+ OCIManifest2For(env, OCINAMESPACE, OCIVERSION)
+ })
+ env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() {
+ env.Component(COMPONENT, func() {
+ env.Version(VERSION, func() {
+ env.Provider(PROVIDER)
+ env.Resource("artifact", "", resourcetypes.OCI_IMAGE, metav1.LocalRelation, func() {
+ env.Access(
+ ociartifact.New(oci.StandardOCIRef(OCIHOST2+".alias", OCINAMESPACE, OCIVERSION)),
+ )
+ })
+ })
+ })
+ })
+}
+
+func DifferentNamespace(env *Builder) {
+ env.OCICommonTransport(ARCH, accessio.FormatDirectory, func() {
+ OCIManifest2For(env, OCINAMESPACE2, OCIVERSION)
+ })
+ env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() {
+ env.Component(COMPONENT, func() {
+ env.Version(VERSION, func() {
+ env.Provider(PROVIDER)
+ env.Resource("artifact", "", resourcetypes.OCI_IMAGE, metav1.LocalRelation, func() {
+ env.Access(
+ relativeociref.New(oci.RelativeOCIRef(OCINAMESPACE2, OCIVERSION)),
+ )
+ })
+ })
+ })
+ })
+}
+
+func DifferentName(env *Builder) {
+ env.OCICommonTransport(ARCH, accessio.FormatDirectory, func() {
+ OCIManifest1For(env, OCINAMESPACE, OCIVERSION)
+ })
+ env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() {
+ env.Component(COMPONENT, func() {
+ env.Version(VERSION, func() {
+ env.Provider(PROVIDER)
+ env.Resource("other", "", resourcetypes.OCI_IMAGE, metav1.LocalRelation, func() {
+ env.Access(
+ ociartifact.New(oci.StandardOCIRef(OCIHOST2+".alias", OCINAMESPACE, OCIVERSION)),
+ )
+ })
+ })
+ })
+ })
+}
+
+func FakeOCIRegBaseFunction(ctx *storagecontext.StorageContext) string {
+ return OCIHOST2 + ".alias"
+}
+
+func CheckBlob(r ocm.ResourceAccess, dig string, size int) {
+ blob := Must(r.BlobAccess())
+ defer Close(blob, "blob")
+
+ ExpectWithOffset(1, int(blob.Size())).To(Equal(size))
+ set := Must(artifactset.OpenFromBlob(accessobj.ACC_READONLY, blob))
+ defer Close(set, "set")
+
+ // data := Must(json.Marshal(set.GetIndex()))
+ // fmt.Printf("%s\n", string(data))
+
+ digest := set.GetMain()
+ ExpectWithOffset(1, digest.Hex()).To(Equal(dig))
+
+ acc := Must(set.GetArtifact(digest.String()))
+ defer Close(acc, "acc")
+
+ ExpectWithOffset(1, acc.IsManifest()).To(BeTrue())
+ ExpectWithOffset(1, acc.Digest().Hex()).To(Equal(dig))
+}
+
+func CheckAritifact(env *Builder, dig string, size int) {
+ repo := Must(ocictf.Open(env, accessobj.ACC_READONLY, ARCH, 0, env))
+ defer Close(repo, "oci repo")
+
+ art := Must(repo.LookupArtifact(OCINAMESPACE, OCIVERSION))
+ defer Close(art, "art")
+
+ ExpectWithOffset(1, art.IsManifest()).To(BeTrue())
+ ExpectWithOffset(1, art.Digest().Hex()).To(Equal(dig))
+}
diff --git a/api/ocm/tools/transfer/transferhandler/suite_test.go b/api/ocm/tools/transfer/transferhandler/suite_test.go
new file mode 100644
index 0000000000..0998312604
--- /dev/null
+++ b/api/ocm/tools/transfer/transferhandler/suite_test.go
@@ -0,0 +1,13 @@
+package transferhandler_test
+
+import (
+ "testing"
+
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+)
+
+func TestConfig(t *testing.T) {
+ RegisterFailHandler(Fail)
+ RunSpecs(t, "Transfer Handler Test Suite")
+}
diff --git a/api/tech/access.go b/api/tech/access.go
new file mode 100644
index 0000000000..0da2e8577c
--- /dev/null
+++ b/api/tech/access.go
@@ -0,0 +1,16 @@
+package tech
+
+// UniformAccessSpecInfo describes a rough uniform specification for
+// an access location or an accessed object. It does not necessarily
+// provide the exact access information required to technically
+// access the object, but just some general information usable
+// independently of the particular technical access specification
+// to figure out some general information in a formal way about the access.
+type UniformAccessSpecInfo struct {
+ Kind string `json:"kind"`
+ Host string `json:"host,omitempty"`
+ Port string `json:"port,omitempty"`
+ Path string `json:"path,omitempty"`
+
+ Info string `json:"info,omitempty"`
+}
diff --git a/api/tech/signing/handlers/plugin/plugin.go b/api/tech/signing/handlers/plugin/plugin.go
new file mode 100644
index 0000000000..a721edfa7f
--- /dev/null
+++ b/api/tech/signing/handlers/plugin/plugin.go
@@ -0,0 +1,52 @@
+package plugin
+
+import (
+ "ocm.software/ocm/api/credentials"
+ "ocm.software/ocm/api/credentials/identity/hostpath"
+ "ocm.software/ocm/api/ocm/plugin"
+ "ocm.software/ocm/api/tech/signing"
+)
+
+type signer struct {
+ plugin plugin.Plugin
+ name string
+}
+
+func NewSigner(p plugin.Plugin, name string) signing.Signer {
+ return &signer{
+ plugin: p,
+ name: name,
+ }
+}
+
+func NewVerifier(p plugin.Plugin, name string) signing.Verifier {
+ return &signer{
+ plugin: p,
+ name: name,
+ }
+}
+
+func (s *signer) Sign(ctx credentials.Context, digest string, sctx signing.SigningContext) (*signing.Signature, error) {
+ cid, err := s.plugin.GetSigningConsumer(s.name, sctx)
+ if err != nil {
+ return nil, err
+ }
+
+ var creds credentials.DirectCredentials
+ if len(cid) != 0 {
+ c, err := credentials.CredentialsForConsumer(ctx, cid, hostpath.IdentityMatcher(cid.Type()))
+ if err != nil {
+ return nil, err
+ }
+ creds = credentials.DirectCredentials(c.Properties())
+ }
+ return s.plugin.Sign(s.name, digest, creds, sctx)
+}
+
+func (s *signer) Verify(digest string, sig *signing.Signature, sctx signing.SigningContext) error {
+ return s.plugin.Verify(s.name, digest, sig, sctx)
+}
+
+func (s *signer) Algorithm() string {
+ return s.name
+}
diff --git a/api/tech/signing/handlers/plugin/signing_test.go b/api/tech/signing/handlers/plugin/signing_test.go
new file mode 100644
index 0000000000..8cd1be67bb
--- /dev/null
+++ b/api/tech/signing/handlers/plugin/signing_test.go
@@ -0,0 +1,190 @@
+//go:build unix
+
+package plugin_test
+
+import (
+ "crypto"
+
+ . "github.com/mandelsoft/goutils/testutils"
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+ "ocm.software/ocm/api/credentials"
+ . "ocm.software/ocm/api/ocm/plugin/testutils"
+ . "ocm.software/ocm/cmds/ocm/testhelper"
+
+ "ocm.software/ocm/api/tech/signing"
+ "ocm.software/ocm/api/tech/signing/handlers/plugin"
+ "ocm.software/ocm/api/tech/signing/handlers/plugin/testdata/plugin/signinghandlers"
+ "ocm.software/ocm/api/tech/signing/signutils"
+ "ocm.software/ocm/api/utils/mime"
+
+ "ocm.software/ocm/api/ocm/extensions/attrs/plugincacheattr"
+ "ocm.software/ocm/api/ocm/plugin/plugins"
+)
+
+const PLUGIN = "signing"
+
+const KEY = "some key"
+
+const DIGEST = "mydigest"
+const DN = "O=acme.org,CN=donald"
+
+var _ = Describe("Signing Command Test Environment", func() {
+ Context("plugin execution", func() {
+ var env *TestEnv
+ var plugindir TempPluginDir
+ var registry plugins.Set
+
+ BeforeEach(func() {
+ env = NewTestEnv(TestData())
+ plugindir = Must(ConfigureTestPlugins(env, "testdata/plugins"))
+ registry = plugincacheattr.Get(env)
+ })
+
+ AfterEach(func() {
+ plugindir.Cleanup()
+ env.Cleanup()
+ })
+
+ It("loads plugin", func() {
+ // Expect(registration.RegisterExtensions(env)).To(Succeed())
+ p := registry.Get(PLUGIN)
+ Expect(p).NotTo(BeNil())
+ Expect(p.Error()).To(Equal(""))
+ })
+
+ It("calls signer", func() {
+ p := registry.Get(PLUGIN)
+
+ sctx := &signing.DefaultSigningContext{
+ Hash: crypto.SHA256,
+ PrivateKey: KEY,
+ PublicKey: nil,
+ Issuer: Must(signutils.ParseDN(DN)),
+ }
+
+ sig := Must(p.Sign(signinghandlers.NAME, DIGEST, nil, sctx))
+ Expect(sig).NotTo(BeNil())
+
+ Expect(sig.Value).To(Equal(DIGEST + ":" + KEY))
+ Expect(sig.MediaType).To(Equal(mime.MIME_TEXT))
+ Expect(sig.Algorithm).To(Equal(signinghandlers.NAME))
+
+ dn := Must(signutils.ParseDN(DN))
+ Expect(sig.Issuer).To(Equal(signutils.DNAsString(*dn)))
+ })
+
+ It("calls verifier", func() {
+ p := registry.Get(PLUGIN)
+
+ sctx := &signing.DefaultSigningContext{
+ Hash: crypto.SHA256,
+ PrivateKey: nil,
+ PublicKey: KEY,
+ Issuer: Must(signutils.ParseDN(DN)),
+ }
+
+ sig := &signing.Signature{
+ Value: DIGEST + ":" + KEY,
+ MediaType: mime.MIME_TEXT,
+ Algorithm: signinghandlers.NAME,
+ Issuer: DN,
+ }
+ MustBeSuccessful(p.Verify(signinghandlers.NAME, DIGEST, sig, sctx))
+ })
+
+ Context("extension with creds", func() {
+ It("calls signer", func() {
+ p := registry.Get(PLUGIN)
+
+ signer := plugin.NewSigner(p, signinghandlers.NAME)
+
+ env.Context.CredentialsContext().SetCredentialsForConsumer(
+ credentials.NewConsumerIdentity(signinghandlers.CID_TYPE),
+ credentials.CredentialsFromList(signinghandlers.SUFFIX_KEY, KEY),
+ )
+ sctx := &signing.DefaultSigningContext{
+ Hash: crypto.SHA256,
+ PrivateKey: signinghandlers.SUFFIX,
+ Issuer: Must(signutils.ParseDN(DN)),
+ }
+
+ sig := Must(signer.Sign(env.Context.CredentialsContext(), DIGEST, sctx))
+ Expect(sig).NotTo(BeNil())
+
+ Expect(sig.Value).To(Equal(DIGEST + ":" + signinghandlers.SUFFIX + ":" + KEY))
+ Expect(sig.MediaType).To(Equal(mime.MIME_TEXT))
+ Expect(sig.Algorithm).To(Equal(signinghandlers.NAME))
+
+ dn := Must(signutils.ParseDN(DN))
+ Expect(sig.Issuer).To(Equal(signutils.DNAsString(*dn)))
+ })
+
+ It("calls verifier", func() {
+ p := registry.Get(PLUGIN)
+
+ verifier := plugin.NewVerifier(p, signinghandlers.NAME)
+
+ sctx := &signing.DefaultSigningContext{
+ Hash: crypto.SHA256,
+ PublicKey: signinghandlers.SUFFIX_KEY + ":" + KEY,
+ Issuer: Must(signutils.ParseDN(DN)),
+ }
+ sig := &signing.Signature{
+ Value: DIGEST + ":" + signinghandlers.SUFFIX_KEY + ":" + KEY,
+ MediaType: mime.MIME_TEXT,
+ Algorithm: signinghandlers.NAME,
+ Issuer: DN,
+ }
+
+ MustBeSuccessful(verifier.Verify(DIGEST, sig, sctx))
+ })
+ })
+
+ Context("extension without creds", func() {
+ It("calls signer", func() {
+ p := registry.Get(PLUGIN)
+
+ signer := plugin.NewSigner(p, signinghandlers.NAME)
+
+ sctx := &signing.DefaultSigningContext{
+ Hash: crypto.SHA256,
+ PrivateKey: KEY,
+ Issuer: Must(signutils.ParseDN(DN)),
+ }
+
+ signer.Sign(nil, DIGEST, sctx)
+
+ sig := Must(signer.Sign(nil, DIGEST, sctx))
+ Expect(sig).NotTo(BeNil())
+
+ Expect(sig.Value).To(Equal(DIGEST + ":" + KEY))
+ Expect(sig.MediaType).To(Equal(mime.MIME_TEXT))
+ Expect(sig.Algorithm).To(Equal(signinghandlers.NAME))
+
+ dn := Must(signutils.ParseDN(DN))
+ Expect(sig.Issuer).To(Equal(signutils.DNAsString(*dn)))
+ })
+
+ It("calls verifier", func() {
+ p := registry.Get(PLUGIN)
+
+ verifier := plugin.NewVerifier(p, signinghandlers.NAME)
+
+ sctx := &signing.DefaultSigningContext{
+ Hash: crypto.SHA256,
+ PublicKey: KEY,
+ Issuer: Must(signutils.ParseDN(DN)),
+ }
+ sig := &signing.Signature{
+ Value: DIGEST + ":" + KEY,
+ MediaType: mime.MIME_TEXT,
+ Algorithm: signinghandlers.NAME,
+ Issuer: DN,
+ }
+
+ MustBeSuccessful(verifier.Verify(DIGEST, sig, sctx))
+ })
+ })
+ })
+})
diff --git a/api/tech/signing/handlers/plugin/suite_test.go b/api/tech/signing/handlers/plugin/suite_test.go
new file mode 100644
index 0000000000..19c18167da
--- /dev/null
+++ b/api/tech/signing/handlers/plugin/suite_test.go
@@ -0,0 +1,13 @@
+package plugin_test
+
+import (
+ "testing"
+
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+)
+
+func TestConfig(t *testing.T) {
+ RegisterFailHandler(Fail)
+ RunSpecs(t, "Signing Plugin Test Suite")
+}
diff --git a/api/tech/signing/handlers/plugin/testdata/context b/api/tech/signing/handlers/plugin/testdata/context
new file mode 100644
index 0000000000..427df2516e
--- /dev/null
+++ b/api/tech/signing/handlers/plugin/testdata/context
@@ -0,0 +1 @@
+{ "privateKey": "737566666978", "hashAlgorithm": "SHA-256" }
diff --git a/api/tech/signing/handlers/plugin/testdata/plugin/app/app.go b/api/tech/signing/handlers/plugin/testdata/plugin/app/app.go
new file mode 100644
index 0000000000..627bcf0366
--- /dev/null
+++ b/api/tech/signing/handlers/plugin/testdata/plugin/app/app.go
@@ -0,0 +1,29 @@
+package app
+
+import (
+ "ocm.software/ocm/api/ocm/plugin/ppi"
+ "ocm.software/ocm/api/ocm/plugin/ppi/cmds"
+ "ocm.software/ocm/api/tech/signing/handlers/plugin/testdata/plugin/signinghandlers"
+ "ocm.software/ocm/api/version"
+)
+
+func New() (ppi.Plugin, error) {
+ p := ppi.NewPlugin("signing", version.Get().String())
+
+ p.SetShort("fake signing plugin")
+ p.SetLong("providing fake signing and verification")
+
+ err := p.RegisterSigningHandler(signinghandlers.New())
+ if err != nil {
+ return nil, err
+ }
+ return p, nil
+}
+
+func Run(args []string, opts ...cmds.Option) error {
+ p, err := New()
+ if err != nil {
+ return err
+ }
+ return cmds.NewPluginCommand(p, opts...).Execute(args)
+}
diff --git a/api/tech/signing/handlers/plugin/testdata/plugin/config/config.go b/api/tech/signing/handlers/plugin/testdata/plugin/config/config.go
new file mode 100644
index 0000000000..9fcd1691b7
--- /dev/null
+++ b/api/tech/signing/handlers/plugin/testdata/plugin/config/config.go
@@ -0,0 +1,23 @@
+package config
+
+import (
+ "encoding/json"
+)
+
+type Config struct {
+ TransferRepositories TransferRepositories `json:"transferRepositories"`
+}
+
+type TransferRepositories struct {
+ Types map[string][]string `json:"types,omitempty"`
+}
+
+func GetConfig(raw json.RawMessage) (interface{}, error) {
+ var cfg Config
+
+ err := json.Unmarshal(raw, &cfg)
+ if err != nil {
+ return nil, err
+ }
+ return &cfg, nil
+}
diff --git a/api/tech/signing/handlers/plugin/testdata/plugin/main.go b/api/tech/signing/handlers/plugin/testdata/plugin/main.go
new file mode 100644
index 0000000000..e1d39492db
--- /dev/null
+++ b/api/tech/signing/handlers/plugin/testdata/plugin/main.go
@@ -0,0 +1,14 @@
+package main
+
+import (
+ "os"
+
+ "ocm.software/ocm/api/tech/signing/handlers/plugin/testdata/plugin/app"
+)
+
+func main() {
+ err := app.Run(os.Args[1:])
+ if err != nil {
+ os.Exit(1)
+ }
+}
diff --git a/api/tech/signing/handlers/plugin/testdata/plugin/signinghandlers/demo.go b/api/tech/signing/handlers/plugin/testdata/plugin/signinghandlers/demo.go
new file mode 100644
index 0000000000..bd0806e24f
--- /dev/null
+++ b/api/tech/signing/handlers/plugin/testdata/plugin/signinghandlers/demo.go
@@ -0,0 +1,112 @@
+package signinghandlers
+
+import (
+ "fmt"
+
+ "github.com/mandelsoft/goutils/errors"
+ "ocm.software/ocm/api/credentials"
+ "ocm.software/ocm/api/ocm/plugin/ppi"
+ "ocm.software/ocm/api/tech/signing"
+ "ocm.software/ocm/api/tech/signing/signutils"
+ "ocm.software/ocm/api/utils/mime"
+)
+
+const (
+ NAME = "demo"
+)
+
+const (
+ CID_TYPE = "TEST"
+ SUFFIX = "suffix"
+ SUFFIX_KEY = "suffix"
+)
+
+func New() ppi.SigningHandler {
+ return ppi.NewSigningHandler(NAME, "fake signing", &signer{}).WithVerifier(&verifier{}).WithCredentials(provider)
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+func provider(sctx signing.SigningContext) credentials.ConsumerIdentity {
+ key, ok := sctx.GetPrivateKey().([]byte)
+ if !ok {
+ return nil
+ }
+ if string(key) == SUFFIX {
+ return credentials.NewConsumerIdentity(CID_TYPE, "host", "localhost")
+ }
+ return nil
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+type signer struct{}
+
+var _ signing.Signer = (*signer)(nil)
+
+func (s signer) Sign(cctx credentials.Context, digest string, sctx signing.SigningContext) (*signing.Signature, error) {
+ var suffix string
+
+ key, ok := sctx.GetPrivateKey().([]byte)
+ if !ok {
+ return nil, fmt.Errorf("invalid private key")
+ }
+
+ if cctx != nil {
+ c, err := credentials.CredentialsForConsumer(cctx, provider(sctx))
+ if err != nil {
+ return nil, err
+ }
+ if c != nil {
+ suffix = c.GetProperty(SUFFIX_KEY)
+ if suffix != "" {
+ suffix = ":" + suffix
+ }
+ }
+ }
+
+ i := ""
+ if sctx.GetIssuer() != nil {
+ i = signutils.DNAsString(*sctx.GetIssuer())
+ }
+ if sctx.GetPrivateKey() == nil {
+ return nil, fmt.Errorf("private key required")
+ }
+ return &signing.Signature{
+ Value: digest + ":" + string(key) + suffix,
+ MediaType: mime.MIME_TEXT,
+ Algorithm: s.Algorithm(),
+ Issuer: i,
+ }, nil
+}
+
+func (s signer) Algorithm() string {
+ return NAME
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+type verifier struct{}
+
+var _ signing.Verifier = (*verifier)(nil)
+
+func (v verifier) Verify(digest string, sig *signing.Signature, sctx signing.SigningContext) error {
+ if sig.Algorithm != NAME {
+ return errors.ErrInvalid(signutils.KIND_SIGN_ALGORITHM, sig.Algorithm)
+ }
+ if sctx.GetPublicKey() == nil {
+ return fmt.Errorf("public key required")
+ }
+ key, ok := sctx.GetPublicKey().([]byte)
+ if !ok {
+ return fmt.Errorf("invalid private key")
+ }
+ if sig.Value != digest+":"+string(key) {
+ return fmt.Errorf("invalid signature %q != %q", sig.Value, digest+":"+string(key))
+ }
+ return nil
+}
+
+func (v verifier) Algorithm() string {
+ return NAME
+}
diff --git a/api/tech/signing/handlers/plugin/testdata/plugins/signing b/api/tech/signing/handlers/plugin/testdata/plugins/signing
new file mode 100755
index 0000000000..97d52eddbb
--- /dev/null
+++ b/api/tech/signing/handlers/plugin/testdata/plugins/signing
@@ -0,0 +1,2 @@
+#!/bin/bash
+go run testdata/plugin/main.go "$@"
\ No newline at end of file
diff --git a/api/utils/accessio/limitwriter.go b/api/utils/accessio/limitwriter.go
index 555f351404..85e26c2232 100644
--- a/api/utils/accessio/limitwriter.go
+++ b/api/utils/accessio/limitwriter.go
@@ -46,7 +46,7 @@ type LimitedBuffer struct {
}
func (b *LimitedBuffer) Exceeded() bool {
- return b.LimitedWriter.N > b.max
+ return b.LimitedWriter.N < 0
}
func (b *LimitedBuffer) Bytes() []byte {
diff --git a/api/utils/cobrautils/flagsets/configoptionset.go b/api/utils/cobrautils/flagsets/configoptionset.go
index ae846beb2f..d964af3c04 100644
--- a/api/utils/cobrautils/flagsets/configoptionset.go
+++ b/api/utils/cobrautils/flagsets/configoptionset.go
@@ -11,6 +11,8 @@ import (
type ConfigOptionType interface {
GetName() string
+ GetDescriptionText() string
+ ValueType() string
GetDescription() string
Create() Option
@@ -293,7 +295,7 @@ func (s *configOptionTypeSet) check(list []ConfigOptionType) error {
for _, o := range list {
old := s.options[o.GetName()]
if old != nil && !old.Equal(o) {
- return fmt.Errorf("option type %s doesn't match (%T<->%T)", o.GetName(), o, old)
+ return fmt.Errorf("option type %q doesn't match ([%T,%s]<->[%T,%s])", o.GetName(), o, o.GetDescription(), old, old.GetDescription())
}
}
return nil
diff --git a/api/utils/cobrautils/flagsets/flagsetscheme/common.go b/api/utils/cobrautils/flagsets/flagsetscheme/common.go
new file mode 100644
index 0000000000..c6c9b80b7a
--- /dev/null
+++ b/api/utils/cobrautils/flagsets/flagsetscheme/common.go
@@ -0,0 +1,19 @@
+package flagsetscheme
+
+import (
+ "ocm.software/ocm/api/utils/cobrautils/flagsets"
+)
+
+type TypeInfo interface {
+ ConfigOptionTypeSetHandler() flagsets.ConfigOptionTypeSetHandler
+ Description() string
+ Format() string
+}
+
+type typeInfoImpl struct {
+ handler flagsets.ConfigOptionTypeSetHandler
+}
+
+func (i *typeInfoImpl) ConfigOptionTypeSetHandler() flagsets.ConfigOptionTypeSetHandler {
+ return i.handler
+}
diff --git a/api/utils/cobrautils/flagsets/flagsetscheme/types_options.go b/api/utils/cobrautils/flagsets/flagsetscheme/options.go
similarity index 69%
rename from api/utils/cobrautils/flagsets/flagsetscheme/types_options.go
rename to api/utils/cobrautils/flagsets/flagsetscheme/options.go
index f6283b976c..f035fbd841 100644
--- a/api/utils/cobrautils/flagsets/flagsetscheme/types_options.go
+++ b/api/utils/cobrautils/flagsets/flagsetscheme/options.go
@@ -7,11 +7,27 @@ import (
"ocm.software/ocm/api/utils/runtime/descriptivetype"
)
+// OptionTargetWrapper is used as target for option functions, it provides
+// setters for fields, which should not be modifiable for a final type object.
+type OptionTargetWrapper[T any] struct {
+ target T
+ info *typeInfoImpl
+}
+
+func NewOptionTargetWrapper[T any](target T, info *typeInfoImpl) *OptionTargetWrapper[T] {
+ return &OptionTargetWrapper[T]{
+ target: target,
+ info: info,
+ }
+}
+
+func (t OptionTargetWrapper[E]) SetConfigHandler(value flagsets.ConfigOptionTypeSetHandler) {
+ t.info.handler = value
+}
+
////////////////////////////////////////////////////////////////////////////////
-// Access Type Options
type OptionTarget interface {
- descriptivetype.OptionTarget
SetConfigHandler(flagsets.ConfigOptionTypeSetHandler)
}
diff --git a/api/utils/cobrautils/flagsets/flagsetscheme/scheme.go b/api/utils/cobrautils/flagsets/flagsetscheme/scheme.go
index f0ed2ea6c3..4698e3f460 100644
--- a/api/utils/cobrautils/flagsets/flagsetscheme/scheme.go
+++ b/api/utils/cobrautils/flagsets/flagsetscheme/scheme.go
@@ -8,19 +8,11 @@ import (
"ocm.software/ocm/api/utils/runtime/descriptivetype"
)
-// VersionTypedObjectType is the appropriately extended type interface
-// based on runtime.VersionTypedObjectType.
-type VersionTypedObjectType[T runtime.VersionedTypedObject] interface {
- descriptivetype.TypedObjectType[T]
-
- ConfigOptionTypeSetHandler() flagsets.ConfigOptionTypeSetHandler
-}
-
////////////////////////////////////////////////////////////////////////////////
-// ExtendedTypeScheme is the appropriately extended scheme interface based on
+// ExtendedProviderTypeScheme is the appropriately extended scheme interface based on
// runtime.TypeScheme supporting an extended config provider interface.
-type ExtendedTypeScheme[T runtime.VersionedTypedObject, R VersionTypedObjectType[T], P flagsets.ConfigTypeOptionSetConfigProvider] interface {
+type ExtendedProviderTypeScheme[T runtime.TypedObject, R TypedObjectType[T], P flagsets.ConfigTypeOptionSetConfigProvider] interface {
descriptivetype.TypeScheme[T, R]
CreateConfigTypeSetConfigProvider() P
@@ -28,11 +20,11 @@ type ExtendedTypeScheme[T runtime.VersionedTypedObject, R VersionTypedObjectType
Unwrap() TypeScheme[T, R]
}
-type _TypeScheme[T runtime.VersionedTypedObject, R VersionTypedObjectType[T]] interface {
+type _TypeScheme[T runtime.TypedObject, R TypedObjectType[T]] interface {
TypeScheme[T, R]
}
-type typeSchemeWrapper[T runtime.VersionedTypedObject, R VersionTypedObjectType[T], P flagsets.ConfigTypeOptionSetConfigProvider] struct {
+type typeSchemeWrapper[T runtime.TypedObject, R TypedObjectType[T], P flagsets.ConfigTypeOptionSetConfigProvider] struct {
_TypeScheme[T, R]
}
@@ -44,24 +36,35 @@ func (s *typeSchemeWrapper[T, R, P]) Unwrap() TypeScheme[T, R] {
return s._TypeScheme
}
+// NewVersionedTypeSchemeWrapper wraps a [VersionedTypeScheme] into a scheme returning a specialized config provider
+// by casting the result. The type scheme constructor provides different implementations based on its
+// arguments. This method here can be used to provide a type scheme returning the correct type.
+func NewVersionedTypeSchemeWrapper[T runtime.VersionedTypedObject, R VersionTypedObjectType[T], P flagsets.ConfigTypeOptionSetConfigProvider](s VersionedTypeScheme[T, R]) ExtendedProviderTypeScheme[T, R, P] {
+ return &typeSchemeWrapper[T, R, P]{s}
+}
+
// NewTypeSchemeWrapper wraps a [TypeScheme] into a scheme returning a specialized config provider
// by casting the result. The type scheme constructor provides different implementations based on its
// arguments. This method here can be used to provide a type scheme returning the correct type.
-func NewTypeSchemeWrapper[T runtime.VersionedTypedObject, R VersionTypedObjectType[T], P flagsets.ConfigTypeOptionSetConfigProvider](s TypeScheme[T, R]) ExtendedTypeScheme[T, R, P] {
+func NewTypeSchemeWrapper[T runtime.TypedObject, R TypedObjectType[T], P flagsets.ConfigTypeOptionSetConfigProvider](s TypeScheme[T, R]) ExtendedProviderTypeScheme[T, R, P] {
return &typeSchemeWrapper[T, R, P]{s}
}
-// TypeScheme is the appropriately extended scheme interface based on
+// VersionedTypeScheme is the appropriately extended scheme interface based on
// runtime.TypeScheme.
-type TypeScheme[T runtime.VersionedTypedObject, R VersionTypedObjectType[T]] interface {
- ExtendedTypeScheme[T, R, flagsets.ConfigTypeOptionSetConfigProvider]
+type VersionedTypeScheme[T runtime.VersionedTypedObject, R VersionTypedObjectType[T]] interface {
+ ExtendedProviderTypeScheme[T, R, flagsets.ConfigTypeOptionSetConfigProvider]
+}
+
+type TypeScheme[T runtime.TypedObject, R TypedObjectType[T]] interface {
+ ExtendedProviderTypeScheme[T, R, flagsets.ConfigTypeOptionSetConfigProvider]
}
-type _typeScheme[T runtime.VersionedTypedObject, R VersionTypedObjectType[T]] interface {
+type _typeScheme[T runtime.TypedObject, R TypedObjectType[T]] interface {
descriptivetype.TypeScheme[T, R]
}
-type typeScheme[T runtime.VersionedTypedObject, R VersionTypedObjectType[T], S TypeScheme[T, R]] struct {
+type typeScheme[T runtime.TypedObject, R TypedObjectType[T], S TypeScheme[T, R]] struct {
cfgname string
description string
group string
@@ -69,16 +72,29 @@ type typeScheme[T runtime.VersionedTypedObject, R VersionTypedObjectType[T], S T
_typeScheme[T, R]
}
-func flagExtender[T runtime.VersionedTypedObject, R VersionTypedObjectType[T]](ty R) string {
+func flagExtender[T runtime.TypedObject, R TypedObjectType[T]](ty R) string {
if h := ty.ConfigOptionTypeSetHandler(); h != nil {
return utils.IndentLines(flagsets.FormatConfigOptions(h), " ")
}
return ""
}
-// NewTypeScheme provides an TypeScheme implementation based on the interfaces
+// NewVersionedTypeScheme provides an VersionedTypeScheme implementation based on the interfaces
+// and the default runtime.TypeScheme implementation.
+func NewVersionedTypeScheme[T runtime.VersionedTypedObject, R VersionTypedObjectType[T], S TypeScheme[T, R]](kindname string, cfgname, typeOption, desc, group string, unknown runtime.Unstructured, acceptUnknown bool, base ...S) VersionedTypeScheme[T, R] {
+ scheme := descriptivetype.NewVersionedTypeScheme[T, R](kindname, flagExtender[T, R], unknown, acceptUnknown, utils.Optional(base...))
+ return &typeScheme[T, R, S]{
+ cfgname: cfgname,
+ description: desc,
+ group: group,
+ typeOption: typeOption,
+ _typeScheme: scheme,
+ }
+}
+
+// NewTypeScheme provides an VersionedTypeScheme implementation based on the interfaces
// and the default runtime.TypeScheme implementation.
-func NewTypeScheme[T runtime.VersionedTypedObject, R VersionTypedObjectType[T], S TypeScheme[T, R]](kindname string, cfgname, typeOption, desc, group string, unknown runtime.Unstructured, acceptUnknown bool, base ...S) TypeScheme[T, R] {
+func NewTypeScheme[T runtime.TypedObject, R TypedObjectType[T], S TypeScheme[T, R]](kindname string, cfgname, typeOption, desc, group string, unknown runtime.Unstructured, acceptUnknown bool, base ...S) TypeScheme[T, R] {
scheme := descriptivetype.NewTypeScheme[T, R](kindname, flagExtender[T, R], unknown, acceptUnknown, utils.Optional(base...))
return &typeScheme[T, R, S]{
cfgname: cfgname,
@@ -110,8 +126,8 @@ func (t *typeScheme[T, R, S]) CreateConfigTypeSetConfigProvider() flagsets.Confi
}
}
if t.BaseScheme() != nil {
- base := t.BaseScheme()
- for _, s := range base.(S).CreateConfigTypeSetConfigProvider().OptionTypeSets() {
+ base := t.BaseScheme().(S)
+ for _, s := range base.CreateConfigTypeSetConfigProvider().OptionTypeSets() {
if prov.GetTypeSet(s.GetName()) == nil {
err := prov.AddTypeSet(s)
if err != nil {
diff --git a/api/utils/cobrautils/flagsets/flagsetscheme/type.go b/api/utils/cobrautils/flagsets/flagsetscheme/type.go
new file mode 100644
index 0000000000..2f82ea96ba
--- /dev/null
+++ b/api/utils/cobrautils/flagsets/flagsetscheme/type.go
@@ -0,0 +1,41 @@
+package flagsetscheme
+
+import (
+ "github.com/mandelsoft/goutils/optionutils"
+
+ "ocm.software/ocm/api/utils/cobrautils/flagsets"
+ "ocm.software/ocm/api/utils/runtime"
+ "ocm.software/ocm/api/utils/runtime/descriptivetype"
+)
+
+// TypedObjectType is the appropriately extended type interface
+// based on runtime.TypedObjectType.
+type TypedObjectType[T runtime.TypedObject] interface {
+ descriptivetype.TypedObjectType[T]
+
+ ConfigOptionTypeSetHandler() flagsets.ConfigOptionTypeSetHandler
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+type TypedObjectTypeObject[E runtime.VersionedTypedObject] struct {
+ *descriptivetype.TypedObjectTypeObject[E]
+ typeInfoImpl
+ validator func(E) error
+}
+
+func NewTypedObjectTypeObject[E runtime.VersionedTypedObject](vt runtime.TypedObjectType[E], opts ...TypeOption) *TypedObjectTypeObject[E] {
+ target := &TypedObjectTypeObject[E]{
+ TypedObjectTypeObject: descriptivetype.NewTypedObjectTypeObject[E](vt, optionutils.FilterMappedOptions[descriptivetype.OptionTarget](opts...)...),
+ }
+ t := NewOptionTargetWrapper[*TypedObjectTypeObject[E]](target, &target.typeInfoImpl)
+ optionutils.ApplyOptions[OptionTarget](t, opts...)
+ return t.target
+}
+
+func (t *TypedObjectTypeObject[E]) Validate(e E) error {
+ if t.validator == nil {
+ return nil
+ }
+ return t.validator(e)
+}
diff --git a/api/utils/cobrautils/flagsets/flagsetscheme/types.go b/api/utils/cobrautils/flagsets/flagsetscheme/types.go
deleted file mode 100644
index 1cf41b4820..0000000000
--- a/api/utils/cobrautils/flagsets/flagsetscheme/types.go
+++ /dev/null
@@ -1,62 +0,0 @@
-package flagsetscheme
-
-import (
- "github.com/mandelsoft/goutils/optionutils"
-
- "ocm.software/ocm/api/utils/cobrautils/flagsets"
- "ocm.software/ocm/api/utils/runtime"
- "ocm.software/ocm/api/utils/runtime/descriptivetype"
-)
-
-type additionalTypeInfo interface {
- ConfigOptionTypeSetHandler() flagsets.ConfigOptionTypeSetHandler
- Description() string
- Format() string
-}
-
-type TypedObjectTypeObject[E runtime.VersionedTypedObject] struct {
- *descriptivetype.TypedObjectTypeObject[E]
- handler flagsets.ConfigOptionTypeSetHandler
- validator func(E) error
-}
-
-var _ additionalTypeInfo = (*TypedObjectTypeObject[runtime.VersionedTypedObject])(nil)
-
-func NewTypedObjectTypeObject[E runtime.VersionedTypedObject](vt runtime.VersionedTypedObjectType[E], opts ...TypeOption) *TypedObjectTypeObject[E] {
- t := NewTypeObjectTarget[E](&TypedObjectTypeObject[E]{
- TypedObjectTypeObject: descriptivetype.NewTypedObjectTypeObject[E](vt),
- })
- optionutils.ApplyOptions[OptionTarget](t, opts...)
- return t.target
-}
-
-func (t *TypedObjectTypeObject[E]) ConfigOptionTypeSetHandler() flagsets.ConfigOptionTypeSetHandler {
- return t.handler
-}
-
-func (t *TypedObjectTypeObject[E]) Validate(e E) error {
- if t.validator == nil {
- return nil
- }
- return t.validator(e)
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
-// TypeObjectTarget is used as target for option functions, it provides
-// setters for fields, which should nor be modifiable for a final type object.
-type TypeObjectTarget[E runtime.VersionedTypedObject] struct {
- *descriptivetype.TypeObjectTarget[E]
- target *TypedObjectTypeObject[E]
-}
-
-func NewTypeObjectTarget[E runtime.VersionedTypedObject](target *TypedObjectTypeObject[E]) *TypeObjectTarget[E] {
- return &TypeObjectTarget[E]{
- target: target,
- TypeObjectTarget: descriptivetype.NewTypeObjectTarget[E](target.TypedObjectTypeObject),
- }
-}
-
-func (t TypeObjectTarget[E]) SetConfigHandler(value flagsets.ConfigOptionTypeSetHandler) {
- t.target.handler = value
-}
diff --git a/api/utils/cobrautils/flagsets/flagsetscheme/versioned.go b/api/utils/cobrautils/flagsets/flagsetscheme/versioned.go
new file mode 100644
index 0000000000..0ee9783882
--- /dev/null
+++ b/api/utils/cobrautils/flagsets/flagsetscheme/versioned.go
@@ -0,0 +1,43 @@
+package flagsetscheme
+
+import (
+ "github.com/mandelsoft/goutils/optionutils"
+
+ "ocm.software/ocm/api/utils/cobrautils/flagsets"
+ "ocm.software/ocm/api/utils/runtime"
+ "ocm.software/ocm/api/utils/runtime/descriptivetype"
+)
+
+// VersionTypedObjectType is the appropriately extended type interface
+// based on runtime.VersionTypedObjectType.
+type VersionTypedObjectType[T runtime.VersionedTypedObject] interface {
+ descriptivetype.VersionedTypedObjectType[T]
+
+ ConfigOptionTypeSetHandler() flagsets.ConfigOptionTypeSetHandler
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+type VersionedTypedObjectTypeObject[E runtime.VersionedTypedObject] struct {
+ *descriptivetype.VersionedTypedObjectTypeObject[E]
+ typeInfoImpl
+ validator func(E) error
+}
+
+var _ TypeInfo = (*VersionedTypedObjectTypeObject[runtime.VersionedTypedObject])(nil)
+
+func NewVersionedTypedObjectTypeObject[E runtime.VersionedTypedObject](vt runtime.VersionedTypedObjectType[E], opts ...TypeOption) *VersionedTypedObjectTypeObject[E] {
+ target := &VersionedTypedObjectTypeObject[E]{
+ VersionedTypedObjectTypeObject: descriptivetype.NewVersionedTypedObjectTypeObject[E](vt, optionutils.FilterMappedOptions[descriptivetype.OptionTarget](opts...)...),
+ }
+ t := NewOptionTargetWrapper[*VersionedTypedObjectTypeObject[E]](target, &target.typeInfoImpl)
+ optionutils.ApplyOptions[OptionTarget](t, opts...)
+ return t.target
+}
+
+func (t *VersionedTypedObjectTypeObject[E]) Validate(e E) error {
+ if t.validator == nil {
+ return nil
+ }
+ return t.validator(e)
+}
diff --git a/api/ocm/extensions/accessmethods/options/registry.go b/api/utils/cobrautils/flagsets/registry.go
similarity index 65%
rename from api/ocm/extensions/accessmethods/options/registry.go
rename to api/utils/cobrautils/flagsets/registry.go
index f45c5dbb16..b5ef53c477 100644
--- a/api/ocm/extensions/accessmethods/options/registry.go
+++ b/api/utils/cobrautils/flagsets/registry.go
@@ -1,4 +1,4 @@
-package options
+package flagsets
import (
"sync"
@@ -13,10 +13,10 @@ const (
KIND_OPTION = "option"
)
-type OptionTypeCreator func(name string, description string) OptionType
+type ConfigOptionTypeCreator func(name string, description string) ConfigOptionType
type ValueTypeInfo struct {
- OptionTypeCreator
+ ConfigOptionTypeCreator
Description string
}
@@ -24,33 +24,31 @@ func (i ValueTypeInfo) GetDescription() string {
return i.Description
}
-type Registry = *registry
-
-var DefaultRegistry = New()
+type ConfigOptionTypeRegistry = *registry
type registry struct {
lock sync.RWMutex
valueTypes map[string]ValueTypeInfo
- optionTypes map[string]OptionType
+ optionTypes map[string]ConfigOptionType
}
-func New() Registry {
+func NewConfigOptionTypeRegistry() ConfigOptionTypeRegistry {
return ®istry{
valueTypes: map[string]ValueTypeInfo{},
- optionTypes: map[string]OptionType{},
+ optionTypes: map[string]ConfigOptionType{},
}
}
-func (r *registry) RegisterOptionType(t OptionType) {
+func (r *registry) RegisterOptionType(t ConfigOptionType) {
r.lock.Lock()
defer r.lock.Unlock()
r.optionTypes[t.GetName()] = t
}
-func (r *registry) RegisterValueType(name string, c OptionTypeCreator, desc string) {
+func (r *registry) RegisterValueType(name string, c ConfigOptionTypeCreator, desc string) {
r.lock.Lock()
defer r.lock.Unlock()
- r.valueTypes[name] = ValueTypeInfo{OptionTypeCreator: c, Description: desc}
+ r.valueTypes[name] = ValueTypeInfo{ConfigOptionTypeCreator: c, Description: desc}
}
func (r *registry) GetValueType(name string) *ValueTypeInfo {
@@ -62,13 +60,13 @@ func (r *registry) GetValueType(name string) *ValueTypeInfo {
return nil
}
-func (r *registry) GetOptionType(name string) OptionType {
+func (r *registry) GetOptionType(name string) ConfigOptionType {
r.lock.RLock()
defer r.lock.RUnlock()
return r.optionTypes[name]
}
-func (r *registry) CreateOptionType(typ, name, desc string) (OptionType, error) {
+func (r *registry) CreateOptionType(typ, name, desc string) (ConfigOptionType, error) {
r.lock.RLock()
defer r.lock.RUnlock()
t, ok := r.valueTypes[typ]
@@ -76,7 +74,7 @@ func (r *registry) CreateOptionType(typ, name, desc string) (OptionType, error)
return nil, errors.ErrUnknown(KIND_OPTIONTYPE, typ)
}
- n := t.OptionTypeCreator(name, desc)
+ n := t.ConfigOptionTypeCreator(name, desc)
o := r.optionTypes[name]
if o != nil {
if o.ValueType() != n.ValueType() {
diff --git a/api/utils/cobrautils/flagsets/registry_test.go b/api/utils/cobrautils/flagsets/registry_test.go
new file mode 100644
index 0000000000..8fe13f02c9
--- /dev/null
+++ b/api/utils/cobrautils/flagsets/registry_test.go
@@ -0,0 +1,53 @@
+package flagsets_test
+
+import (
+ . "github.com/mandelsoft/goutils/testutils"
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+ "ocm.software/ocm/api/utils/cobrautils/flagsets"
+
+ "ocm.software/ocm/api/ocm/extensions/accessmethods/options"
+)
+
+var _ = Describe("registry", func() {
+ var reg flagsets.ConfigOptionTypeRegistry
+
+ BeforeEach(func() {
+ reg = flagsets.NewConfigOptionTypeRegistry()
+ })
+
+ It("sets and retrieves type", func() {
+ reg.RegisterValueType("string", flagsets.NewStringOptionType, "string")
+
+ t := reg.GetValueType("string")
+ Expect(t).NotTo(BeNil())
+
+ o := Must(reg.CreateOptionType("string", "test", "some test"))
+ Expect(o.GetName()).To(Equal("test"))
+ Expect(o.GetDescription()).To(Equal("[*string*] some test"))
+ })
+
+ It("sets and retrieves option", func() {
+ reg.RegisterOptionType(options.HostnameOption)
+
+ t := reg.GetOptionType(options.HostnameOption.GetName())
+ Expect(t).NotTo(BeNil())
+ })
+
+ It("creates merges a new type", func() {
+ reg.RegisterValueType("string", flagsets.NewStringOptionType, "string")
+ reg.RegisterOptionType(options.HostnameOption)
+
+ o := Must(reg.CreateOptionType("string", options.HostnameOption.GetName(), "some test"))
+ Expect(o).To(BeIdenticalTo(options.HostnameOption))
+ })
+
+ It("fails creating existing", func() {
+ reg.RegisterValueType("string", flagsets.NewStringOptionType, "string")
+ reg.RegisterValueType("int", flagsets.NewIntOptionType, "int")
+ reg.RegisterOptionType(options.HostnameOption)
+
+ _, err := reg.CreateOptionType("int", options.HostnameOption.GetName(), "some test")
+ MustFailWithMessage(err, "option \"accessHostname\" already exists")
+ })
+})
diff --git a/api/utils/cobrautils/flagsets/types.go b/api/utils/cobrautils/flagsets/types.go
index b3aaacf097..f205fa9179 100644
--- a/api/utils/cobrautils/flagsets/types.go
+++ b/api/utils/cobrautils/flagsets/types.go
@@ -1,7 +1,7 @@
package flagsets
import (
- "reflect"
+ "fmt"
"github.com/spf13/pflag"
@@ -10,9 +10,41 @@ import (
"ocm.software/ocm/api/utils/cobrautils/groups"
)
+const (
+ TYPE_STRING = "string"
+ TYPE_STRINGARRAY = "[]string"
+ TYPE_STRING2STRING = "string=string"
+ TYPE_INT = "int"
+ TYPE_BOOL = "bool"
+ TYPE_YAML = "YAML"
+ TYPE_STRINGMAPYAML = "map[string]YAML"
+ TYPE_STRING2YAML = "string=YAML"
+ TYPE_STRING2STRINGSLICE = "string=string,string"
+ TYPE_STRINGCOLONSTRINGSLICE = "string:string,string"
+ TYPE_BYTES = "[]byte"
+ TYPE_IDENTITYPATH = "[]identity"
+)
+
+func SetBaseTypes(r ConfigOptionTypeRegistry) ConfigOptionTypeRegistry {
+ r.RegisterValueType(TYPE_STRING, NewStringOptionType, "string value")
+ r.RegisterValueType(TYPE_STRINGARRAY, NewStringArrayOptionType, "list of string values")
+ r.RegisterValueType(TYPE_STRING2STRING, NewStringMapOptionType, "string map defined by dedicated assignments")
+ r.RegisterValueType(TYPE_INT, NewIntOptionType, "integer value")
+ r.RegisterValueType(TYPE_BOOL, NewBoolOptionType, "boolean flag")
+ r.RegisterValueType(TYPE_YAML, NewYAMLOptionType, "JSON or YAML document string")
+ r.RegisterValueType(TYPE_STRINGMAPYAML, NewValueMapYAMLOptionType, "JSON or YAML map")
+ r.RegisterValueType(TYPE_STRING2YAML, NewValueMapOptionType, "string map with arbitrary values defined by dedicated assignments")
+ r.RegisterValueType(TYPE_STRING2STRINGSLICE, NewStringSliceMapOptionType, "string map defined by dedicated assignment of comma separated strings")
+ r.RegisterValueType(TYPE_STRINGCOLONSTRINGSLICE, NewStringSliceMapColonOptionType, "string map defined by dedicated assignment of comma separated strings")
+ r.RegisterValueType(TYPE_BYTES, NewBytesOptionType, "byte value")
+ r.RegisterValueType(TYPE_IDENTITYPATH, NewIdentityPathOptionType, "identity path")
+ return r
+}
+
type TypeOptionBase struct {
name string
description string
+ valueType string
}
func (b *TypeOptionBase) GetName() string {
@@ -20,9 +52,23 @@ func (b *TypeOptionBase) GetName() string {
}
func (b *TypeOptionBase) GetDescription() string {
+ return fmt.Sprintf("[*%s*] %s", b.ValueType(), b.description)
+}
+
+func (b *TypeOptionBase) GetDescriptionText() string {
return b.description
}
+func (o *TypeOptionBase) ValueType() string {
+ return o.valueType
+}
+
+func (o *TypeOptionBase) Equal(t ConfigOptionType) bool {
+ return o.name == t.GetName() &&
+ o.description == t.GetDescriptionText() &&
+ o.valueType == t.ValueType()
+}
+
////////////////////////////////////////////////////////////////////////////////
type OptionBase struct {
@@ -44,7 +90,7 @@ func (b *OptionBase) GetName() string {
}
func (b *OptionBase) Description() string {
- return b.otyp.GetDescription()
+ return b.otyp.GetDescriptionText()
}
func (b *OptionBase) Changed() bool {
@@ -80,14 +126,10 @@ type StringOptionType struct {
func NewStringOptionType(name string, description string) ConfigOptionType {
return &StringOptionType{
- TypeOptionBase: TypeOptionBase{name, description},
+ TypeOptionBase: TypeOptionBase{name, description, TYPE_STRING},
}
}
-func (s *StringOptionType) Equal(optionType ConfigOptionType) bool {
- return reflect.DeepEqual(s, optionType)
-}
-
func (s *StringOptionType) Create() Option {
return &StringOption{
OptionBase: NewOptionBase(s),
@@ -102,7 +144,7 @@ type StringOption struct {
var _ Option = (*StringOption)(nil)
func (o *StringOption) AddFlags(fs *pflag.FlagSet) {
- o.TweakFlag(flag.StringVarPF(fs, &o.value, o.otyp.GetName(), "", "", o.otyp.GetDescription()))
+ o.TweakFlag(flag.StringVarPF(fs, &o.value, o.otyp.GetName(), "", "", o.otyp.GetDescriptionText()))
}
func (o *StringOption) Value() interface{} {
@@ -117,14 +159,10 @@ type StringArrayOptionType struct {
func NewStringArrayOptionType(name string, description string) ConfigOptionType {
return &StringArrayOptionType{
- TypeOptionBase: TypeOptionBase{name, description},
+ TypeOptionBase: TypeOptionBase{name, description, TYPE_STRINGARRAY},
}
}
-func (s *StringArrayOptionType) Equal(optionType ConfigOptionType) bool {
- return reflect.DeepEqual(s, optionType)
-}
-
func (s *StringArrayOptionType) Create() Option {
return &StringArrayOption{
OptionBase: NewOptionBase(s),
@@ -139,87 +177,13 @@ type StringArrayOption struct {
var _ Option = (*StringArrayOption)(nil)
func (o *StringArrayOption) AddFlags(fs *pflag.FlagSet) {
- o.TweakFlag(flag.StringArrayVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescription()))
+ o.TweakFlag(flag.StringArrayVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescriptionText()))
}
func (o *StringArrayOption) Value() interface{} {
return o.value
}
-// PathOptionType //////////////////////////////////////////////////////////////////////////////
-
-type PathOptionType struct {
- TypeOptionBase
-}
-
-func NewPathOptionType(name string, description string) ConfigOptionType {
- return &PathOptionType{
- TypeOptionBase: TypeOptionBase{name, description},
- }
-}
-
-func (s *PathOptionType) Equal(optionType ConfigOptionType) bool {
- return reflect.DeepEqual(s, optionType)
-}
-
-func (s *PathOptionType) Create() Option {
- return &PathOption{
- OptionBase: NewOptionBase(s),
- }
-}
-
-type PathOption struct {
- OptionBase
- value string
-}
-
-var _ Option = (*PathOption)(nil)
-
-func (o *PathOption) AddFlags(fs *pflag.FlagSet) {
- o.TweakFlag(flag.PathVarPF(fs, &o.value, o.otyp.GetName(), "", "", o.otyp.GetDescription()))
-}
-
-func (o *PathOption) Value() interface{} {
- return o.value
-}
-
-// PathArrayOptionType //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-type PathArrayOptionType struct {
- TypeOptionBase
-}
-
-func NewPathArrayOptionType(name string, description string) ConfigOptionType {
- return &PathArrayOptionType{
- TypeOptionBase: TypeOptionBase{name, description},
- }
-}
-
-func (s *PathArrayOptionType) Equal(optionType ConfigOptionType) bool {
- return reflect.DeepEqual(s, optionType)
-}
-
-func (s *PathArrayOptionType) Create() Option {
- return &PathArrayOption{
- OptionBase: NewOptionBase(s),
- }
-}
-
-type PathArrayOption struct {
- OptionBase
- value []string
-}
-
-var _ Option = (*PathArrayOption)(nil)
-
-func (o *PathArrayOption) AddFlags(fs *pflag.FlagSet) {
- o.TweakFlag(flag.PathArrayVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescription()))
-}
-
-func (o *PathArrayOption) Value() interface{} {
- return o.value
-}
-
////////////////////////////////////////////////////////////////////////////////
type BoolOptionType struct {
@@ -228,14 +192,10 @@ type BoolOptionType struct {
func NewBoolOptionType(name string, description string) ConfigOptionType {
return &BoolOptionType{
- TypeOptionBase: TypeOptionBase{name, description},
+ TypeOptionBase: TypeOptionBase{name, description, TYPE_BOOL},
}
}
-func (s *BoolOptionType) Equal(optionType ConfigOptionType) bool {
- return reflect.DeepEqual(s, optionType)
-}
-
func (s *BoolOptionType) Create() Option {
return &BoolOption{
OptionBase: NewOptionBase(s),
@@ -250,7 +210,7 @@ type BoolOption struct {
var _ Option = (*BoolOption)(nil)
func (o *BoolOption) AddFlags(fs *pflag.FlagSet) {
- o.TweakFlag(flag.BoolVarPF(fs, &o.value, o.otyp.GetName(), "", false, o.otyp.GetDescription()))
+ o.TweakFlag(flag.BoolVarPF(fs, &o.value, o.otyp.GetName(), "", false, o.otyp.GetDescriptionText()))
}
func (o *BoolOption) Value() interface{} {
@@ -265,14 +225,10 @@ type IntOptionType struct {
func NewIntOptionType(name string, description string) ConfigOptionType {
return &IntOptionType{
- TypeOptionBase: TypeOptionBase{name, description},
+ TypeOptionBase: TypeOptionBase{name, description, TYPE_INT},
}
}
-func (s *IntOptionType) Equal(optionType ConfigOptionType) bool {
- return reflect.DeepEqual(s, optionType)
-}
-
func (s *IntOptionType) Create() Option {
return &IntOption{
OptionBase: NewOptionBase(s),
@@ -287,7 +243,7 @@ type IntOption struct {
var _ Option = (*IntOption)(nil)
func (o *IntOption) AddFlags(fs *pflag.FlagSet) {
- o.TweakFlag(flag.IntVarPF(fs, &o.value, o.otyp.GetName(), "", 0, o.otyp.GetDescription()))
+ o.TweakFlag(flag.IntVarPF(fs, &o.value, o.otyp.GetName(), "", 0, o.otyp.GetDescriptionText()))
}
func (o *IntOption) Value() interface{} {
@@ -302,14 +258,10 @@ type YAMLOptionType struct {
func NewYAMLOptionType(name string, description string) ConfigOptionType {
return &YAMLOptionType{
- TypeOptionBase: TypeOptionBase{name, description},
+ TypeOptionBase: TypeOptionBase{name, description, TYPE_YAML},
}
}
-func (s *YAMLOptionType) Equal(optionType ConfigOptionType) bool {
- return reflect.DeepEqual(s, optionType)
-}
-
func (s *YAMLOptionType) Create() Option {
return &YAMLOption{
OptionBase: NewOptionBase(s),
@@ -324,7 +276,7 @@ type YAMLOption struct {
var _ Option = (*YAMLOption)(nil)
func (o *YAMLOption) AddFlags(fs *pflag.FlagSet) {
- o.TweakFlag(flag.YAMLVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescription()))
+ o.TweakFlag(flag.YAMLVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescriptionText()))
}
func (o *YAMLOption) Value() interface{} {
@@ -339,14 +291,10 @@ type ValueMapYAMLOptionType struct {
func NewValueMapYAMLOptionType(name string, description string) ConfigOptionType {
return &ValueMapYAMLOptionType{
- TypeOptionBase: TypeOptionBase{name, description},
+ TypeOptionBase: TypeOptionBase{name, description, TYPE_STRINGMAPYAML},
}
}
-func (s *ValueMapYAMLOptionType) Equal(optionType ConfigOptionType) bool {
- return reflect.DeepEqual(s, optionType)
-}
-
func (s *ValueMapYAMLOptionType) Create() Option {
return &ValueMapYAMLOption{
OptionBase: NewOptionBase(s),
@@ -361,7 +309,7 @@ type ValueMapYAMLOption struct {
var _ Option = (*ValueMapYAMLOption)(nil)
func (o *ValueMapYAMLOption) AddFlags(fs *pflag.FlagSet) {
- o.TweakFlag(flag.YAMLVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescription()))
+ o.TweakFlag(flag.YAMLVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescriptionText()))
}
func (o *ValueMapYAMLOption) Value() interface{} {
@@ -376,14 +324,10 @@ type ValueMapOptionType struct {
func NewValueMapOptionType(name string, description string) ConfigOptionType {
return &ValueMapOptionType{
- TypeOptionBase: TypeOptionBase{name, description},
+ TypeOptionBase: TypeOptionBase{name, description, TYPE_STRING2YAML},
}
}
-func (s *ValueMapOptionType) Equal(optionType ConfigOptionType) bool {
- return reflect.DeepEqual(s, optionType)
-}
-
func (s *ValueMapOptionType) Create() Option {
return &ValueMapOption{
OptionBase: NewOptionBase(s),
@@ -398,7 +342,7 @@ type ValueMapOption struct {
var _ Option = (*ValueMapOption)(nil)
func (o *ValueMapOption) AddFlags(fs *pflag.FlagSet) {
- o.TweakFlag(flag.StringToValueVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescription()))
+ o.TweakFlag(flag.StringToValueVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescriptionText()))
}
func (o *ValueMapOption) Value() interface{} {
@@ -413,14 +357,10 @@ type StringMapOptionType struct {
func NewStringMapOptionType(name string, description string) ConfigOptionType {
return &StringMapOptionType{
- TypeOptionBase: TypeOptionBase{name, description},
+ TypeOptionBase: TypeOptionBase{name, description, TYPE_STRING2STRING},
}
}
-func (s *StringMapOptionType) Equal(optionType ConfigOptionType) bool {
- return reflect.DeepEqual(s, optionType)
-}
-
func (s *StringMapOptionType) Create() Option {
return &StringMapOption{
OptionBase: NewOptionBase(s),
@@ -435,7 +375,7 @@ type StringMapOption struct {
var _ Option = (*StringMapOption)(nil)
func (o *StringMapOption) AddFlags(fs *pflag.FlagSet) {
- o.TweakFlag(flag.StringToStringVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescription()))
+ o.TweakFlag(flag.StringToStringVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescriptionText()))
}
func (o *StringMapOption) Value() interface{} {
@@ -450,14 +390,10 @@ type BytesOptionType struct {
func NewBytesOptionType(name string, description string) ConfigOptionType {
return &BytesOptionType{
- TypeOptionBase: TypeOptionBase{name, description},
+ TypeOptionBase: TypeOptionBase{name, description, TYPE_BYTES},
}
}
-func (s *BytesOptionType) Equal(optionType ConfigOptionType) bool {
- return reflect.DeepEqual(s, optionType)
-}
-
func (s *BytesOptionType) Create() Option {
return &BytesOption{
OptionBase: NewOptionBase(s),
@@ -472,7 +408,7 @@ type BytesOption struct {
var _ Option = (*BytesOption)(nil)
func (o *BytesOption) AddFlags(fs *pflag.FlagSet) {
- o.TweakFlag(flag.BytesBase64VarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescription()))
+ o.TweakFlag(flag.BytesBase64VarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescriptionText()))
}
func (o *BytesOption) Value() interface{} {
@@ -487,14 +423,10 @@ type StringSliceMapOptionType struct {
func NewStringSliceMapOptionType(name string, description string) ConfigOptionType {
return &StringSliceMapOptionType{
- TypeOptionBase: TypeOptionBase{name, description},
+ TypeOptionBase: TypeOptionBase{name, description, TYPE_STRING2STRINGSLICE},
}
}
-func (s *StringSliceMapOptionType) Equal(optionType ConfigOptionType) bool {
- return reflect.DeepEqual(s, optionType)
-}
-
func (s *StringSliceMapOptionType) Create() Option {
return &StringSliceMapOption{
OptionBase: NewOptionBase(s),
@@ -509,7 +441,7 @@ type StringSliceMapOption struct {
var _ Option = (*StringSliceMapOption)(nil)
func (o *StringSliceMapOption) AddFlags(fs *pflag.FlagSet) {
- o.TweakFlag(flag.StringToStringSliceVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescription()))
+ o.TweakFlag(flag.StringToStringSliceVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescriptionText()))
}
func (o *StringSliceMapOption) Value() interface{} {
@@ -524,14 +456,10 @@ type StringSliceMapColonOptionType struct {
func NewStringSliceMapColonOptionType(name string, description string) ConfigOptionType {
return &StringSliceMapColonOptionType{
- TypeOptionBase: TypeOptionBase{name, description},
+ TypeOptionBase: TypeOptionBase{name, description, TYPE_STRINGCOLONSTRINGSLICE},
}
}
-func (s *StringSliceMapColonOptionType) Equal(optionType ConfigOptionType) bool {
- return reflect.DeepEqual(s, optionType)
-}
-
func (s *StringSliceMapColonOptionType) Create() Option {
return &StringSliceMapColonOption{
OptionBase: NewOptionBase(s),
@@ -546,7 +474,7 @@ type StringSliceMapColonOption struct {
var _ Option = (*StringSliceMapColonOption)(nil)
func (o *StringSliceMapColonOption) AddFlags(fs *pflag.FlagSet) {
- o.TweakFlag(flag.StringColonStringSliceVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescription()))
+ o.TweakFlag(flag.StringColonStringSliceVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescriptionText()))
}
func (o *StringSliceMapColonOption) Value() interface{} {
@@ -561,14 +489,10 @@ type IdentityPathOptionType struct {
func NewIdentityPathOptionType(name string, description string) ConfigOptionType {
return &IdentityPathOptionType{
- TypeOptionBase: TypeOptionBase{name, description},
+ TypeOptionBase: TypeOptionBase{name, description, TYPE_IDENTITYPATH},
}
}
-func (s *IdentityPathOptionType) Equal(optionType ConfigOptionType) bool {
- return reflect.DeepEqual(s, optionType)
-}
-
func (s *IdentityPathOptionType) Create() Option {
return &IdentityPathOption{
OptionBase: NewOptionBase(s),
@@ -583,7 +507,7 @@ type IdentityPathOption struct {
var _ Option = (*IdentityPathOption)(nil)
func (o *IdentityPathOption) AddFlags(fs *pflag.FlagSet) {
- o.TweakFlag(flag.IdentityPathVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescription()))
+ o.TweakFlag(flag.IdentityPathVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescriptionText()))
}
func (o *IdentityPathOption) Value() interface{} {
diff --git a/api/utils/runtime/descriptivetype/common.go b/api/utils/runtime/descriptivetype/common.go
new file mode 100644
index 0000000000..b42a95a3c0
--- /dev/null
+++ b/api/utils/runtime/descriptivetype/common.go
@@ -0,0 +1,27 @@
+package descriptivetype
+
+// TypeInfo is the interface extension for descriptive types.
+type TypeInfo interface {
+ Description() string
+ Format() string
+}
+
+type typeInfoImpl struct {
+ description string
+ format string
+}
+
+var _ TypeInfo = (*typeInfoImpl)(nil)
+
+func (i *typeInfoImpl) Description() string {
+ return i.description
+}
+
+func (i *typeInfoImpl) Format() string {
+ return i.format
+}
+
+// DescriptionExtender provides an additional description for a type object
+// which is appended to the format description in the scheme description
+// for the type in question.
+type DescriptionExtender[T any] func(t T) string
diff --git a/api/utils/runtime/descriptivetype/options.go b/api/utils/runtime/descriptivetype/options.go
index 730b05a4bc..6dab03c221 100644
--- a/api/utils/runtime/descriptivetype/options.go
+++ b/api/utils/runtime/descriptivetype/options.go
@@ -2,28 +2,31 @@ package descriptivetype
import (
"github.com/mandelsoft/goutils/optionutils"
-
- "ocm.software/ocm/api/utils/runtime"
)
////////////////////////////////////////////////////////////////////////////////
-// TypeObjectTarget is used as target for option functions, it provides
+// OptionTargetWrapper is used as target for option functions, it provides
// setters for fields, which should not be modifiable for a final type object.
-type TypeObjectTarget[E runtime.VersionedTypedObject] struct {
- target *TypedObjectTypeObject[E]
+type OptionTargetWrapper[T any] struct {
+ target T
+ info *typeInfoImpl
+}
+
+func NewOptionTargetWrapper[T any](target T, info *typeInfoImpl) *OptionTargetWrapper[T] {
+ return &OptionTargetWrapper[T]{target, info}
}
-func NewTypeObjectTarget[E runtime.VersionedTypedObject](target *TypedObjectTypeObject[E]) *TypeObjectTarget[E] {
- return &TypeObjectTarget[E]{target}
+func (t *OptionTargetWrapper[T]) SetDescription(value string) {
+ t.info.description = value
}
-func (t *TypeObjectTarget[E]) SetDescription(value string) {
- t.target.description = value
+func (t *OptionTargetWrapper[T]) SetFormat(value string) {
+ t.info.format = value
}
-func (t *TypeObjectTarget[E]) SetFormat(value string) {
- t.target.format = value
+func (t *OptionTargetWrapper[T]) Target() T {
+ return t.target
}
////////////////////////////////////////////////////////////////////////////////
diff --git a/api/utils/runtime/descriptivetype/scheme.go b/api/utils/runtime/descriptivetype/scheme.go
new file mode 100644
index 0000000000..193fa97975
--- /dev/null
+++ b/api/utils/runtime/descriptivetype/scheme.go
@@ -0,0 +1,132 @@
+package descriptivetype
+
+import (
+ "fmt"
+ "strings"
+
+ "ocm.software/ocm/api/utils"
+ "ocm.software/ocm/api/utils/runtime"
+)
+
+// TypeScheme is the appropriately extended scheme interface based on
+// runtime.TypeScheme. Based on the additional type info a complete
+// scheme description can be created calling the Describe method.
+type TypeScheme[T runtime.TypedObject, R TypedObjectType[T]] interface {
+ runtime.TypeScheme[T, R]
+
+ Describe() string
+}
+
+type _typeScheme[T runtime.TypedObject, R runtime.TypedObjectType[T]] interface {
+ runtime.TypeScheme[T, R] // for goland to be able to accept extender argument type
+}
+
+type typeScheme[T runtime.TypedObject, R TypedObjectType[T], S runtime.TypeScheme[T, R]] struct {
+ name string
+ extender DescriptionExtender[R]
+ versioned bool
+ _typeScheme[T, R]
+}
+
+func MustNewDefaultTypeScheme[T runtime.TypedObject, R TypedObjectType[T], S TypeScheme[T, R]](name string, extender DescriptionExtender[R], unknown runtime.Unstructured, acceptUnknown bool, defaultdecoder runtime.TypedObjectDecoder[T], base ...TypeScheme[T, R]) TypeScheme[T, R] {
+ scheme := runtime.MustNewDefaultTypeScheme[T, R](unknown, acceptUnknown, defaultdecoder, utils.Optional(base...))
+ return &typeScheme[T, R, S]{
+ name: name,
+ extender: extender,
+ _typeScheme: scheme,
+ }
+}
+
+// NewTypeScheme provides an TypeScheme implementation based on the interfaces
+// and the default runtime.TypeScheme implementation.
+func NewTypeScheme[T runtime.TypedObject, R TypedObjectType[T], S TypeScheme[T, R]](name string, extender DescriptionExtender[R], unknown runtime.Unstructured, acceptUnknown bool, base ...S) TypeScheme[T, R] {
+ scheme := runtime.MustNewDefaultTypeScheme[T, R](unknown, acceptUnknown, nil, utils.Optional(base...))
+ return &typeScheme[T, R, S]{
+ name: name,
+ extender: extender,
+ _typeScheme: scheme,
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+func (t *typeScheme[T, R, S]) KnownTypes() runtime.KnownTypes[T, R] {
+ return t._typeScheme.KnownTypes() // Goland
+}
+
+func (t *typeScheme[T, R, S]) Describe() string {
+ s := ""
+ type method struct {
+ desc string
+ versions map[string]string
+ more string
+ }
+
+ descs := map[string]*method{}
+
+ // gather info for kinds and versions
+ for _, n := range t.KnownTypeNames() {
+ var kind, vers string
+ if t.versioned {
+ kind, vers = runtime.KindVersion(n)
+ } else {
+ kind = n
+ }
+
+ info := descs[kind]
+ if info == nil {
+ info = &method{versions: map[string]string{}}
+ descs[kind] = info
+ }
+
+ if vers == "" {
+ vers = "v1"
+ }
+ if _, ok := info.versions[vers]; !ok {
+ info.versions[vers] = ""
+ }
+
+ ty := t.GetType(n)
+
+ if t.extender != nil {
+ more := t.extender(ty)
+ if more != "" {
+ info.more = more
+ }
+ }
+ desc := ty.Description()
+ if desc != "" {
+ info.desc = desc
+ }
+
+ desc = ty.Format()
+ if desc != "" {
+ info.versions[vers] = desc
+ }
+ }
+
+ for _, tn := range utils.StringMapKeys(descs) {
+ info := descs[tn]
+ desc := strings.Trim(info.desc, "\n")
+ if desc != "" {
+ s = fmt.Sprintf("%s\n- %s %s\n\n%s\n\n", s, t.name, tn, utils.IndentLines(desc, " "))
+
+ format := ""
+ for _, f := range utils.StringMapKeys(info.versions) {
+ desc = strings.Trim(info.versions[f], "\n")
+ if desc != "" {
+ if t.versioned {
+ format = fmt.Sprintf("%s\n- Version %s\n\n%s\n", format, f, utils.IndentLines(desc, " "))
+ } else {
+ s += utils.IndentLines(desc, " ")
+ }
+ }
+ }
+ if format != "" {
+ s += fmt.Sprintf(" The following versions are supported:\n%s\n", strings.Trim(utils.IndentLines(format, " "), "\n"))
+ }
+ }
+ s += info.more
+ }
+ return s
+}
diff --git a/api/utils/runtime/descriptivetype/type.go b/api/utils/runtime/descriptivetype/type.go
index 6a5ad78eef..c58508331d 100644
--- a/api/utils/runtime/descriptivetype/type.go
+++ b/api/utils/runtime/descriptivetype/type.go
@@ -1,175 +1,34 @@
package descriptivetype
import (
- "fmt"
- "strings"
-
- "ocm.software/ocm/api/utils"
"ocm.software/ocm/api/utils/runtime"
)
-// DescriptionExtender provides an additional description for a type object
-// which is appended to the format description in the scheme description
-// for the type in question.
-type DescriptionExtender[T any] func(t T) string
-
// TypedObjectType is the appropriately extended type interface
// based on runtime.VersionTypedObjectType providing support for a functional and
// format description.
-type TypedObjectType[T runtime.VersionedTypedObject] interface {
- runtime.VersionedTypedObjectType[T]
-
- Description() string
- Format() string
+type TypedObjectType[T runtime.TypedObject] interface {
+ runtime.TypedObjectType[T]
+ TypeInfo
}
////////////////////////////////////////////////////////////////////////////////
-// TypeScheme is the appropriately extended scheme interface based on
-// runtime.TypeScheme. Based on the additional type info a complete
-// scheme description can be created calling the Describe method.
-type TypeScheme[T runtime.VersionedTypedObject, R TypedObjectType[T]] interface {
- runtime.TypeScheme[T, R]
-
- Describe() string
-}
-
-type _typeScheme[T runtime.VersionedTypedObject, R TypedObjectType[T]] interface {
- runtime.TypeScheme[T, R] // for goland to be able to accept extender argument type
-}
-
-type typeScheme[T runtime.VersionedTypedObject, R TypedObjectType[T], S TypeScheme[T, R]] struct {
- name string
- extender DescriptionExtender[R]
- _typeScheme[T, R]
-}
-
-func MustNewDefaultTypeScheme[T runtime.VersionedTypedObject, R TypedObjectType[T], S TypeScheme[T, R]](name string, extender DescriptionExtender[R], unknown runtime.Unstructured, acceptUnknown bool, defaultdecoder runtime.TypedObjectDecoder[T], base ...TypeScheme[T, R]) TypeScheme[T, R] {
- scheme := runtime.MustNewDefaultTypeScheme[T, R](unknown, acceptUnknown, defaultdecoder, utils.Optional(base...))
- return &typeScheme[T, R, S]{
- name: name,
- extender: extender,
- _typeScheme: scheme,
- }
+type TypedObjectTypeObject[T runtime.TypedObject] struct {
+ runtime.TypedObjectType[T]
+ typeInfoImpl
+ validator func(T) error
}
-// NewTypeScheme provides an TypeScheme implementation based on the interfaces
-// and the default runtime.TypeScheme implementation.
-func NewTypeScheme[T runtime.VersionedTypedObject, R TypedObjectType[T], S TypeScheme[T, R]](name string, extender DescriptionExtender[R], unknown runtime.Unstructured, acceptUnknown bool, base ...S) TypeScheme[T, R] {
- scheme := runtime.MustNewDefaultTypeScheme[T, R](unknown, acceptUnknown, nil, utils.Optional(base...))
- return &typeScheme[T, R, S]{
- name: name,
- extender: extender,
- _typeScheme: scheme,
- }
-}
-
-func (t *typeScheme[T, R, S]) KnownTypes() runtime.KnownTypes[T, R] {
- return t._typeScheme.KnownTypes() // Goland
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
-func (t *typeScheme[T, R, S]) Describe() string {
- s := ""
- type method struct {
- desc string
- versions map[string]string
- more string
- }
-
- descs := map[string]*method{}
-
- // gather info for kinds and versions
- for _, n := range t.KnownTypeNames() {
- kind, vers := runtime.KindVersion(n)
-
- info := descs[kind]
- if info == nil {
- info = &method{versions: map[string]string{}}
- descs[kind] = info
- }
-
- if vers == "" {
- vers = "v1"
- }
- if _, ok := info.versions[vers]; !ok {
- info.versions[vers] = ""
- }
-
- ty := t.GetType(n)
-
- if t.extender != nil {
- more := t.extender(ty)
- if more != "" {
- info.more = more
- }
- }
- desc := ty.Description()
- if desc != "" {
- info.desc = desc
- }
-
- desc = ty.Format()
- if desc != "" {
- info.versions[vers] = desc
- }
+func NewTypedObjectTypeObject[E runtime.VersionedTypedObject](vt runtime.TypedObjectType[E], opts ...Option) *TypedObjectTypeObject[E] {
+ target := &TypedObjectTypeObject[E]{
+ TypedObjectType: vt,
}
-
- for _, tn := range utils.StringMapKeys(descs) {
- info := descs[tn]
- desc := strings.Trim(info.desc, "\n")
- if desc != "" {
- s = fmt.Sprintf("%s\n- %s %s\n\n%s\n\n", s, t.name, tn, utils.IndentLines(desc, " "))
-
- format := ""
- for _, f := range utils.StringMapKeys(info.versions) {
- desc = strings.Trim(info.versions[f], "\n")
- if desc != "" {
- format = fmt.Sprintf("%s\n- Version %s\n\n%s\n", format, f, utils.IndentLines(desc, " "))
- }
- }
- if format != "" {
- s += fmt.Sprintf(" The following versions are supported:\n%s\n", strings.Trim(utils.IndentLines(format, " "), "\n"))
- }
- }
- s += info.more
- }
- return s
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
-type descriptiveTypeInfo interface {
- Description() string
- Format() string
-}
-
-type TypedObjectTypeObject[T runtime.VersionedTypedObject] struct {
- runtime.VersionedTypedObjectType[T]
- description string
- format string
- validator func(T) error
-}
-
-var _ descriptiveTypeInfo = (*TypedObjectTypeObject[runtime.VersionedTypedObject])(nil)
-
-func NewTypedObjectTypeObject[E runtime.VersionedTypedObject](vt runtime.VersionedTypedObjectType[E], opts ...Option) *TypedObjectTypeObject[E] {
- t := NewTypeObjectTarget[E](&TypedObjectTypeObject[E]{
- VersionedTypedObjectType: vt,
- })
+ t := NewOptionTargetWrapper(target, &target.typeInfoImpl)
for _, o := range opts {
o.ApplyTo(t)
}
- return t.target
-}
-
-func (t *TypedObjectTypeObject[T]) Description() string {
- return t.description
-}
-
-func (t *TypedObjectTypeObject[T]) Format() string {
- return t.format
+ return target
}
func (t *TypedObjectTypeObject[T]) Validate(e T) error {
diff --git a/api/utils/runtime/descriptivetype/versionedtype.go b/api/utils/runtime/descriptivetype/versionedtype.go
new file mode 100644
index 0000000000..9c75b8877b
--- /dev/null
+++ b/api/utils/runtime/descriptivetype/versionedtype.go
@@ -0,0 +1,71 @@
+package descriptivetype
+
+import (
+ "ocm.software/ocm/api/utils"
+ "ocm.software/ocm/api/utils/runtime"
+)
+
+// VersionedTypedObjectType is the appropriately extended type interface
+// based on runtime.VersionTypedObjectType providing support for a functional and
+// format description.
+type VersionedTypedObjectType[T runtime.VersionedTypedObject] interface {
+ runtime.VersionedTypedObjectType[T]
+ TypeInfo
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// VersionedTypeScheme is the appropriately extended scheme interface based on
+// runtime.TypeScheme. Based on the additional type info a complete
+// scheme description can be created calling the Describe method.
+type VersionedTypeScheme[T runtime.VersionedTypedObject, R VersionedTypedObjectType[T]] interface {
+ TypeScheme[T, R]
+}
+
+func MustNewDefaultVersionedTypeScheme[T runtime.VersionedTypedObject, R VersionedTypedObjectType[T], S VersionedTypeScheme[T, R]](name string, extender DescriptionExtender[R], unknown runtime.Unstructured, acceptUnknown bool, defaultdecoder runtime.TypedObjectDecoder[T], base ...VersionedTypeScheme[T, R]) VersionedTypeScheme[T, R] {
+ scheme := runtime.MustNewDefaultTypeScheme[T, R](unknown, acceptUnknown, defaultdecoder, utils.Optional(base...))
+ return &typeScheme[T, R, S]{
+ name: name,
+ extender: extender,
+ _typeScheme: scheme,
+ versioned: true,
+ }
+}
+
+// NewVersionedTypeScheme provides an TypeScheme implementation based on the interfaces
+// and the default runtime.TypeScheme implementation.
+func NewVersionedTypeScheme[T runtime.VersionedTypedObject, R VersionedTypedObjectType[T], S VersionedTypeScheme[T, R]](name string, extender DescriptionExtender[R], unknown runtime.Unstructured, acceptUnknown bool, base ...S) VersionedTypeScheme[T, R] {
+ scheme := runtime.MustNewDefaultTypeScheme[T, R](unknown, acceptUnknown, nil, utils.Optional(base...))
+ return &typeScheme[T, R, S]{
+ name: name,
+ extender: extender,
+ _typeScheme: scheme,
+ versioned: true,
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+type VersionedTypedObjectTypeObject[T runtime.VersionedTypedObject] struct {
+ runtime.VersionedTypedObjectType[T]
+ typeInfoImpl
+ validator func(T) error
+}
+
+func NewVersionedTypedObjectTypeObject[E runtime.VersionedTypedObject](vt runtime.VersionedTypedObjectType[E], opts ...Option) *VersionedTypedObjectTypeObject[E] {
+ target := &VersionedTypedObjectTypeObject[E]{
+ VersionedTypedObjectType: vt,
+ }
+ t := NewOptionTargetWrapper(target, &target.typeInfoImpl)
+ for _, o := range opts {
+ o.ApplyTo(t)
+ }
+ return target
+}
+
+func (t *VersionedTypedObjectTypeObject[T]) Validate(e T) error {
+ if t.validator == nil {
+ return nil
+ }
+ return t.validator(e)
+}
diff --git a/api/utils/runtime/json.go b/api/utils/runtime/json.go
new file mode 100644
index 0000000000..fe5f175aa5
--- /dev/null
+++ b/api/utils/runtime/json.go
@@ -0,0 +1,30 @@
+package runtime
+
+import (
+ "encoding/json"
+)
+
+func ToJSON(in interface{}) (json.RawMessage, error) {
+ if in == nil {
+ return nil, nil
+ }
+
+ var raw interface{}
+ switch c := in.(type) {
+ case json.RawMessage:
+ return c, nil
+ case []byte:
+ err := DefaultYAMLEncoding.Unmarshal(c, &raw)
+ if err != nil {
+ return nil, err
+ }
+ case string:
+ err := DefaultYAMLEncoding.Unmarshal([]byte(c), &raw)
+ if err != nil {
+ return nil, err
+ }
+ default:
+ raw = c
+ }
+ return json.Marshal(raw)
+}
diff --git a/api/utils/runtime/scheme.go b/api/utils/runtime/scheme.go
index 61ebf4d682..52bdd5092a 100644
--- a/api/utils/runtime/scheme.go
+++ b/api/utils/runtime/scheme.go
@@ -215,6 +215,33 @@ func NewDefaultScheme[T TypedObject, R TypedObjectDecoder[T]](protoUnstr Unstruc
}, nil
}
+type Copyable[T any] interface {
+ Copy() T
+}
+
+// CopyScheme copies a Copyable scheme.
+// This is not a method on Scheme, because it would be propagated by embedding
+// a scheme to get the traditional methods, but would return an internal
+// implementation detail.
+func CopyScheme[T TypedObject, R TypedObjectDecoder[T]](s Scheme[T, R]) Scheme[T, R] {
+ if c, ok := s.(Copyable[Scheme[T, R]]); ok {
+ return c.Copy()
+ }
+ return nil
+}
+
+func (d *defaultScheme[T, R]) Copy() Scheme[T, R] {
+ scheme := &defaultScheme[T, R]{
+ instance: d.instance,
+ unstructured: d.unstructured,
+ defaultdecoder: d.defaultdecoder,
+ acceptUnknown: d.acceptUnknown,
+ types: KnownTypes[T, R]{},
+ }
+ scheme.AddKnownTypes(d)
+ return scheme
+}
+
func (d *defaultScheme[T, R]) BaseScheme() Scheme[T, R] {
return d.base
}
diff --git a/api/utils/utils.go b/api/utils/utils.go
index b51a48bce2..bdffb1d2f8 100644
--- a/api/utils/utils.go
+++ b/api/utils/utils.go
@@ -355,3 +355,13 @@ func AsBool(b *bool, def ...bool) bool {
}
return b != nil && *b
}
+
+func Crop(s string, n int) string {
+ lines := strings.Split(s, "\n")
+ for i, l := range lines {
+ if len(l) >= 2 {
+ lines[i] = l[n:]
+ }
+ }
+ return strings.Join(lines, "\n")
+}
diff --git a/cmds/demoplugin/accessmethods/demo.go b/cmds/demoplugin/accessmethods/demo.go
index 3cce5f5fae..079e74a3bc 100644
--- a/cmds/demoplugin/accessmethods/demo.go
+++ b/cmds/demoplugin/accessmethods/demo.go
@@ -13,6 +13,7 @@ import (
"ocm.software/ocm/api/credentials/cpi"
"ocm.software/ocm/api/ocm/extensions/accessmethods/options"
"ocm.software/ocm/api/ocm/plugin/ppi"
+ "ocm.software/ocm/api/tech"
"ocm.software/ocm/api/tech/oci/identity"
"ocm.software/ocm/api/utils/cobrautils/flagsets"
"ocm.software/ocm/api/utils/runtime"
@@ -36,7 +37,7 @@ type AccessMethod struct {
ppi.AccessMethodBase
}
-var PathOption = options.NewStringOptionType("accessPath", "path in temp repository")
+var PathOption = flagsets.NewStringOptionType("accessPath", "path in temp repository")
var _ ppi.AccessMethod = (*AccessMethod)(nil)
@@ -46,8 +47,8 @@ func New() ppi.AccessMethod {
}
}
-func (a *AccessMethod) Options() []options.OptionType {
- return []options.OptionType{
+func (a *AccessMethod) Options() []flagsets.ConfigOptionType {
+ return []flagsets.ConfigOptionType{
options.MediatypeOption,
PathOption,
}
@@ -87,6 +88,11 @@ func (a *AccessMethod) ValidateSpecification(p ppi.Plugin, spec ppi.AccessSpec)
}
info.Short = "temp file " + my.Path
info.Hint = "temp file " + my.Path
+ // optional information for extended access spec interface
+ info.Info = &tech.UniformAccessSpecInfo{
+ Kind: my.GetKind(),
+ Info: my.Path,
+ }
return &info, nil
}
diff --git a/cmds/demoplugin/valuesets/check.go b/cmds/demoplugin/valuesets/check.go
index 0e75372aff..729428ca6c 100644
--- a/cmds/demoplugin/valuesets/check.go
+++ b/cmds/demoplugin/valuesets/check.go
@@ -7,7 +7,6 @@ import (
"github.com/mandelsoft/goutils/errors"
"github.com/mandelsoft/goutils/set"
- "ocm.software/ocm/api/ocm/extensions/accessmethods/options"
"ocm.software/ocm/api/ocm/plugin/descriptor"
"ocm.software/ocm/api/ocm/plugin/ppi"
"ocm.software/ocm/api/utils"
@@ -36,8 +35,8 @@ const (
var status = set.New[string](STATUS_PASSED, STATUS_FAILED, STATUS_SKIPPED)
var (
- StatusOption = options.NewStringMapOptionType("checkStatus", out.Sprintf("status value for check (%s)", strings.Join(utils.StringMapKeys(status), ", ")))
- MessageOption = options.NewStringMapOptionType("checkMessage", "message for check")
+ StatusOption = flagsets.NewStringMapOptionType("checkStatus", out.Sprintf("status value for check (%s)", strings.Join(utils.StringMapKeys(status), ", ")))
+ MessageOption = flagsets.NewStringMapOptionType("checkMessage", "message for check")
)
type ValueSet struct {
@@ -56,8 +55,8 @@ The status entry has the following format:
}
}
-func (v ValueSet) Options() []options.OptionType {
- return []options.OptionType{
+func (v ValueSet) Options() []flagsets.ConfigOptionType {
+ return []flagsets.ConfigOptionType{
StatusOption,
MessageOption,
}
diff --git a/cmds/ocm/app/app.go b/cmds/ocm/app/app.go
index d0266a7f4d..f9e6beab60 100644
--- a/cmds/ocm/app/app.go
+++ b/cmds/ocm/app/app.go
@@ -30,6 +30,7 @@ import (
creds "ocm.software/ocm/cmds/ocm/commands/misccmds/credentials"
"ocm.software/ocm/cmds/ocm/commands/ocicmds"
"ocm.software/ocm/cmds/ocm/commands/ocmcmds"
+ inputplugins "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin"
"ocm.software/ocm/cmds/ocm/commands/ocmcmds/componentarchive"
"ocm.software/ocm/cmds/ocm/commands/ocmcmds/components"
"ocm.software/ocm/cmds/ocm/commands/ocmcmds/names"
@@ -393,6 +394,7 @@ func (o *CLIOptions) Complete() error {
if err != nil {
return err
}
+ inputplugins.RegisterPlugins(o.Context)
return o.Context.ConfigContext().Validate()
}
diff --git a/cmds/ocm/commands/ocmcmds/common/addhdlrs/comp/elements.go b/cmds/ocm/commands/ocmcmds/common/addhdlrs/comp/elements.go
index c5fdf9a3b3..946a028b40 100644
--- a/cmds/ocm/commands/ocmcmds/common/addhdlrs/comp/elements.go
+++ b/cmds/ocm/commands/ocmcmds/common/addhdlrs/comp/elements.go
@@ -117,11 +117,13 @@ func (h *ResourceSpecHandler) Add(ctx clictx.Context, ictx inputs.Context, elem
cd := cv.GetDescriptor()
- opts := h.srchandler.AsOptionSet()[0].(*addhdlrs.Options)
- if !opts.Replace {
- cd.Resources = nil
- cd.Sources = nil
- cd.References = nil
+ if len(h.srchandler.AsOptionSet()) > 0 {
+ opts := h.srchandler.AsOptionSet()[0].(*addhdlrs.Options)
+ if !opts.Replace {
+ cd.Resources = nil
+ cd.Sources = nil
+ cd.References = nil
+ }
}
schema := h.schema.Schema
diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/cpi/helper.go b/cmds/ocm/commands/ocmcmds/common/inputs/cpi/helper.go
index 2646ae99a7..0bf2e21f60 100644
--- a/cmds/ocm/commands/ocmcmds/common/inputs/cpi/helper.go
+++ b/cmds/ocm/commands/ocmcmds/common/inputs/cpi/helper.go
@@ -109,12 +109,12 @@ func (s *ProcessSpec) ProcessBlob(ctx inputs.Context, acc blobaccess.DataAccess,
}
func AddProcessSpecOptionTypes(set flagsets.ConfigOptionTypeSetHandler) {
- set.AddOptionType(options.MediaTypeOption)
+ set.AddOptionType(options.MediatypeOption)
set.AddOptionType(options.CompressOption)
}
func AddProcessSpecConfig(opts flagsets.ConfigOptions, config flagsets.Config) error {
- flagsets.AddFieldByOptionP(opts, options.MediaTypeOption, config, "mediaType")
+ flagsets.AddFieldByOptionP(opts, options.MediatypeOption, config, "mediaType")
flagsets.AddFieldByOptionP(opts, options.CompressOption, config, "compress")
return nil
}
diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/extend_test.go b/cmds/ocm/commands/ocmcmds/common/inputs/extend_test.go
new file mode 100644
index 0000000000..eeef9db3d5
--- /dev/null
+++ b/cmds/ocm/commands/ocmcmds/common/inputs/extend_test.go
@@ -0,0 +1,160 @@
+package inputs_test
+
+import (
+ "fmt"
+
+ . "github.com/mandelsoft/goutils/testutils"
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+ clictx "ocm.software/ocm/api/cli"
+ "ocm.software/ocm/api/datacontext"
+
+ "github.com/spf13/pflag"
+ "k8s.io/apimachinery/pkg/util/validation/field"
+
+ "ocm.software/ocm/api/utils/blobaccess/blobaccess"
+ "ocm.software/ocm/api/utils/cobrautils/flagsets"
+ "ocm.software/ocm/api/utils/mime"
+ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs"
+ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi"
+ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options"
+ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact"
+)
+
+var _ = Describe("Input Type Extension Test Environment", func() {
+ var (
+ scheme inputs.InputTypeScheme
+ itype = inputs.NewInputType(TYPE, &Spec{}, "", ConfigHandler())
+ flags *pflag.FlagSet
+ opts flagsets.ConfigOptions
+ )
+
+ Context("registry", func() {
+ BeforeEach(func() {
+ scheme = inputs.NewInputTypeScheme(nil, inputs.DefaultInputTypeScheme)
+ scheme.Register(itype)
+ flags = &pflag.FlagSet{}
+ opts = scheme.CreateConfigTypeSetConfigProvider().CreateOptions()
+ opts.AddFlags(flags)
+ })
+
+ It("is not in base", func() {
+ scheme = inputs.DefaultInputTypeScheme
+ Expect(scheme.GetInputType(TYPE)).To(BeNil())
+ })
+
+ It("derives base input type", func() {
+ prov := scheme.CreateConfigTypeSetConfigProvider()
+ MustBeSuccessful(flagsets.ParseOptionsFor(flags,
+ flagsets.OptionSpec(prov.GetTypeOptionType(), ociartifact.TYPE),
+ flagsets.OptionSpec(options.PathOption, "ghcr.io/open-component-model/image:v1.0"),
+ flagsets.OptionSpec(options.PlatformsOption, "linux/amd64"),
+ flagsets.OptionSpec(options.PlatformsOption, "/arm64"),
+ ))
+ cfg := Must(prov.GetConfigFor(opts))
+ fmt.Printf("selected input options: %+v\n", cfg)
+
+ spec := Must(scheme.GetInputSpecFor(cfg))
+ Expect(spec).To(Equal(ociartifact.New("ghcr.io/open-component-model/image:v1.0", "linux/amd64", "/arm64")))
+ })
+
+ It("uses extended input type", func() {
+ prov := scheme.CreateConfigTypeSetConfigProvider()
+ MustBeSuccessful(flagsets.ParseOptionsFor(flags,
+ flagsets.OptionSpec(prov.GetTypeOptionType(), TYPE),
+ flagsets.OptionSpec(options.PathOption, "ghcr.io/open-component-model/image:v1.0"),
+ ))
+ cfg := Must(prov.GetConfigFor(opts))
+ fmt.Printf("selected input options: %+v\n", cfg)
+
+ spec := Must(scheme.GetInputSpecFor(cfg))
+ Expect(spec).To(Equal(New("ghcr.io/open-component-model/image:v1.0")))
+ })
+ })
+
+ Context("cli context", func() {
+ var ctx clictx.Context
+
+ BeforeEach(func() {
+ ctx = clictx.New(datacontext.MODE_EXTENDED)
+ scheme = inputs.For(ctx)
+ scheme.Register(itype)
+ flags = &pflag.FlagSet{}
+ opts = scheme.CreateConfigTypeSetConfigProvider().CreateOptions()
+ opts.AddFlags(flags)
+ })
+
+ It("is not in base", func() {
+ scheme = inputs.For(clictx.DefaultContext())
+ Expect(scheme.GetInputType(TYPE)).To(BeNil())
+ })
+
+ It("derives base input type", func() {
+ prov := scheme.CreateConfigTypeSetConfigProvider()
+ MustBeSuccessful(flagsets.ParseOptionsFor(flags,
+ flagsets.OptionSpec(prov.GetTypeOptionType(), ociartifact.TYPE),
+ flagsets.OptionSpec(options.PathOption, "ghcr.io/open-component-model/image:v1.0"),
+ flagsets.OptionSpec(options.PlatformsOption, "linux/amd64"),
+ flagsets.OptionSpec(options.PlatformsOption, "/arm64"),
+ ))
+ cfg := Must(prov.GetConfigFor(opts))
+ fmt.Printf("selected input options: %+v\n", cfg)
+
+ spec := Must(scheme.GetInputSpecFor(cfg))
+ Expect(spec).To(Equal(ociartifact.New("ghcr.io/open-component-model/image:v1.0", "linux/amd64", "/arm64")))
+ })
+
+ It("uses extended input type", func() {
+ prov := scheme.CreateConfigTypeSetConfigProvider()
+ MustBeSuccessful(flagsets.ParseOptionsFor(flags,
+ flagsets.OptionSpec(prov.GetTypeOptionType(), TYPE),
+ flagsets.OptionSpec(options.PathOption, "ghcr.io/open-component-model/image:v1.0"),
+ ))
+ cfg := Must(prov.GetConfigFor(opts))
+ fmt.Printf("selected input options: %+v\n", cfg)
+
+ spec := Must(scheme.GetInputSpecFor(cfg))
+ Expect(spec).To(Equal(New("ghcr.io/open-component-model/image:v1.0")))
+ })
+ })
+})
+
+////////////////////////////////////////////////////////////////////////////////
+// test input
+
+const TYPE = "testinput"
+
+type Spec struct {
+ // PathSpec holds the repository path and tag of the image in the docker daemon
+ cpi.PathSpec
+}
+
+var _ inputs.InputSpec = (*Spec)(nil)
+
+func New(pathtag string) *Spec {
+ return &Spec{
+ PathSpec: cpi.NewPathSpec(TYPE, pathtag),
+ }
+}
+
+func (s *Spec) Validate(fldPath *field.Path, ctx inputs.Context, inputFilePath string) field.ErrorList {
+ allErrs := s.PathSpec.Validate(fldPath, ctx, inputFilePath)
+ return allErrs
+}
+
+func (s *Spec) GetBlob(ctx inputs.Context, info inputs.InputResourceInfo) (blobaccess.BlobAccess, string, error) {
+ blob := blobaccess.ForString(mime.MIME_TEXT, s.Path)
+ return blob, "", nil
+}
+
+func ConfigHandler() flagsets.ConfigOptionTypeSetHandler {
+ return cpi.NewMediaFileSpecOptionType(TYPE, AddConfig,
+ options.PathOption, options.HintOption, options.PlatformsOption)
+}
+
+func AddConfig(opts flagsets.ConfigOptions, config flagsets.Config) error {
+ if err := cpi.AddPathSpecConfig(opts, config); err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/inputtype.go b/cmds/ocm/commands/ocmcmds/common/inputs/inputtype.go
index 39a233d97f..ec60849a2c 100644
--- a/cmds/ocm/commands/ocmcmds/common/inputs/inputtype.go
+++ b/cmds/ocm/commands/ocmcmds/common/inputs/inputtype.go
@@ -15,6 +15,7 @@ import (
"ocm.software/ocm/api/utils"
"ocm.software/ocm/api/utils/blobaccess"
"ocm.software/ocm/api/utils/cobrautils/flagsets"
+ ocmlog "ocm.software/ocm/api/utils/logging"
common "ocm.software/ocm/api/utils/misc"
"ocm.software/ocm/api/utils/runtime"
)
@@ -157,42 +158,47 @@ func (t *DefaultInputType) ApplyConfig(opts flagsets.ConfigOptions, config flags
type InputTypeScheme interface {
runtime.Scheme[InputSpec, InputType]
- ConfigTypeSetConfigProvider() flagsets.ConfigTypeOptionSetConfigProvider
- flagsets.ConfigProvider
+ Copy() InputTypeScheme
+
+ CreateConfigTypeSetConfigProvider() flagsets.ConfigTypeOptionSetConfigProvider
GetInputType(name string) InputType
Register(atype InputType)
- GetInputSpecFor(opts flagsets.ConfigOptions) (InputSpec, error)
+ GetInputSpecFor(flagsets.Config) (InputSpec, error)
DecodeInputSpec(data []byte, unmarshaler runtime.Unmarshaler) (InputSpec, error)
CreateInputSpec(obj runtime.TypedObject) (InputSpec, error)
}
type inputTypeScheme struct {
runtime.Scheme[InputSpec, InputType]
- optionTypes flagsets.ConfigTypeOptionSetConfigProvider
}
-func NewInputTypeScheme(defaultRepoDecoder runtime.TypedObjectDecoder[InputSpec]) InputTypeScheme {
- scheme := runtime.MustNewDefaultScheme[InputSpec, InputType](&UnknownInputSpec{}, false, defaultRepoDecoder)
- prov := flagsets.NewTypedConfigProvider("input", "blob input specification", "inputType")
- prov.AddGroups("Input Specification Options")
- return &inputTypeScheme{scheme, prov}
-}
+func NewInputTypeScheme(defaultRepoDecoder runtime.TypedObjectDecoder[InputSpec], base ...InputTypeScheme) InputTypeScheme {
+ var b runtime.Scheme[InputSpec, InputType] = utils.Optional(base...)
-func (t *inputTypeScheme) ConfigTypeSetConfigProvider() flagsets.ConfigTypeOptionSetConfigProvider {
- return t.optionTypes
+ scheme := runtime.MustNewDefaultScheme[InputSpec, InputType](&UnknownInputSpec{}, false, defaultRepoDecoder, b)
+ return &inputTypeScheme{scheme}
}
-func (t *inputTypeScheme) CreateOptions() flagsets.ConfigOptions {
- return t.optionTypes.CreateOptions()
+func (t *inputTypeScheme) Copy() InputTypeScheme {
+ scheme := runtime.CopyScheme(t.Scheme)
+ return &inputTypeScheme{scheme}
}
-func (t *inputTypeScheme) GetInputSpecFor(opts flagsets.ConfigOptions) (InputSpec, error) {
- cfg, err := t.GetConfigFor(opts)
- if err != nil {
- return nil, err
+func (t *inputTypeScheme) CreateConfigTypeSetConfigProvider() flagsets.ConfigTypeOptionSetConfigProvider {
+ prov := flagsets.NewTypedConfigProvider("input", "blob input specification", "inputType")
+ prov.AddGroups("Input Specification Options")
+ for _, p := range t.KnownTypes() {
+ err := prov.AddTypeSet(p.ConfigOptionTypeSetHandler())
+ if err != nil {
+ ocmlog.Logger(REALM).LogError(err, "cannot compose type CLI options", "type", p.GetType())
+ }
}
+ return prov
+}
+
+func (t *inputTypeScheme) GetInputSpecFor(cfg flagsets.Config) (InputSpec, error) {
data, err := json.Marshal(cfg)
if err != nil {
return nil, err
@@ -200,10 +206,6 @@ func (t *inputTypeScheme) GetInputSpecFor(opts flagsets.ConfigOptions) (InputSpe
return t.DecodeInputSpec(data, runtime.DefaultJSONEncoding)
}
-func (t *inputTypeScheme) GetConfigFor(opts flagsets.ConfigOptions) (flagsets.Config, error) {
- return t.optionTypes.GetConfigFor(opts)
-}
-
func (t *inputTypeScheme) GetInputType(name string) InputType {
d := t.GetDecoder(name)
if d == nil {
@@ -217,7 +219,6 @@ func (t *inputTypeScheme) Register(rtype InputType) {
return
}
t.RegisterByDecoder(rtype.GetType(), rtype)
- t.optionTypes.AddTypeSet(rtype.ConfigOptionTypeSetHandler())
}
func (t *inputTypeScheme) DecodeInputSpec(data []byte, unmarshaler runtime.Unmarshaler) (InputSpec, error) {
diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/inputtype_test.go b/cmds/ocm/commands/ocmcmds/common/inputs/inputtype_test.go
index a124ee0a33..73f72e4cb8 100644
--- a/cmds/ocm/commands/ocmcmds/common/inputs/inputtype_test.go
+++ b/cmds/ocm/commands/ocmcmds/common/inputs/inputtype_test.go
@@ -5,7 +5,6 @@ import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
-
"ocm.software/ocm/api/utils/mime"
"ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs"
"ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/file"
diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/logging.go b/cmds/ocm/commands/ocmcmds/common/inputs/logging.go
new file mode 100644
index 0000000000..76aa65a28b
--- /dev/null
+++ b/cmds/ocm/commands/ocmcmds/common/inputs/logging.go
@@ -0,0 +1,7 @@
+package inputs
+
+import (
+ ocmlog "ocm.software/ocm/api/utils/logging"
+)
+
+var REALM = ocmlog.DefineSubRealm("OCM Input Handling", "inputs")
diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/options/init.go b/cmds/ocm/commands/ocmcmds/common/inputs/options/init.go
new file mode 100644
index 0000000000..387ff5c1f3
--- /dev/null
+++ b/cmds/ocm/commands/ocmcmds/common/inputs/options/init.go
@@ -0,0 +1,12 @@
+package options
+
+import (
+ "ocm.software/ocm/api/utils/cobrautils/flagsets"
+)
+
+var DefaultRegistry = flagsets.SetBaseTypes(flagsets.NewConfigOptionTypeRegistry())
+
+func RegisterOption(o flagsets.ConfigOptionType) flagsets.ConfigOptionType {
+ DefaultRegistry.RegisterOptionType(o)
+ return o
+}
diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/options/standard.go b/cmds/ocm/commands/ocmcmds/common/inputs/options/standard.go
index 7ed29a25f3..5fcc6e6315 100644
--- a/cmds/ocm/commands/ocmcmds/common/inputs/options/standard.go
+++ b/cmds/ocm/commands/ocmcmds/common/inputs/options/standard.go
@@ -6,73 +6,73 @@ import (
)
var (
- HintOption = options.HintOption
- MediaTypeOption = options.MediatypeOption
+ HintOption = RegisterOption(options.HintOption)
+ MediatypeOption = RegisterOption(options.MediatypeOption)
- URLOption = options.URLOption
- HTTPHeaderOption = options.HTTPHeaderOption
- HTTPVerbOption = options.HTTPVerbOption
- HTTPBodyOption = options.HTTPBodyOption
- HTTPRedirectOption = options.HTTPRedirectOption
+ URLOption = RegisterOption(options.URLOption)
+ HTTPHeaderOption = RegisterOption(options.HTTPHeaderOption)
+ HTTPVerbOption = RegisterOption(options.HTTPVerbOption)
+ HTTPBodyOption = RegisterOption(options.HTTPBodyOption)
+ HTTPRedirectOption = RegisterOption(options.HTTPRedirectOption)
- GroupOption = options.GroupOption
- ArtifactOption = options.ArtifactOption
- ClassifierOption = options.ClassifierOption
- ExtensionOption = options.ExtensionOption
+ GroupOption = RegisterOption(options.GroupOption)
+ ArtifactOption = RegisterOption(options.ArtifactOption)
+ ClassifierOption = RegisterOption(options.ClassifierOption)
+ ExtensionOption = RegisterOption(options.ExtensionOption)
- RegistryOption = options.NPMRegistryOption
- PackageOption = options.NPMPackageOption
- PackageVersionOption = options.NPMVersionOption
+ RegistryOption = RegisterOption(options.NPMRegistryOption)
+ PackageOption = RegisterOption(options.NPMPackageOption)
+ PackageVersionOption = RegisterOption(options.NPMVersionOption)
- IdentityPathOption = options.IdentityPathOption
+ IdentityPathOption = RegisterOption(options.IdentityPathOption)
)
// string options.
var (
- VersionOption = flagsets.NewStringOptionType("inputVersion", "version info for inputs")
- TextOption = flagsets.NewStringOptionType("inputText", "utf8 text")
- HelmRepositoryOption = flagsets.NewStringOptionType("inputHelmRepository", "helm repository base URL")
+ VersionOption = RegisterOption(flagsets.NewStringOptionType("inputVersion", "version info for inputs"))
+ TextOption = RegisterOption(flagsets.NewStringOptionType("inputText", "utf8 text"))
+ HelmRepositoryOption = RegisterOption(flagsets.NewStringOptionType("inputHelmRepository", "helm repository base URL"))
)
var (
- VariantsOption = flagsets.NewStringArrayOptionType("inputVariants", "(platform) variants for inputs")
- PlatformsOption = flagsets.NewStringArrayOptionType("inputPlatforms", "input filter for image platforms ([os]/[architecture])")
+ VariantsOption = RegisterOption(flagsets.NewStringArrayOptionType("inputVariants", "(platform) variants for inputs"))
+ PlatformsOption = RegisterOption(flagsets.NewStringArrayOptionType("inputPlatforms", "input filter for image platforms ([os]/[architecture])"))
)
// path options.
var (
- PathOption = flagsets.NewPathOptionType("inputPath", "path field for input")
+ PathOption = RegisterOption(flagsets.NewStringOptionType("inputPath", "path field for input"))
)
var (
- IncludeOption = flagsets.NewPathArrayOptionType("inputIncludes", "includes (path) for inputs")
- ExcludeOption = flagsets.NewPathArrayOptionType("inputExcludes", "excludes (path) for inputs")
- LibrariesOption = flagsets.NewPathArrayOptionType("inputLibraries", "library path for inputs")
+ IncludeOption = RegisterOption(flagsets.NewStringArrayOptionType("inputIncludes", "includes (path) for inputs"))
+ ExcludeOption = RegisterOption(flagsets.NewStringArrayOptionType("inputExcludes", "excludes (path) for inputs"))
+ LibrariesOption = RegisterOption(flagsets.NewStringArrayOptionType("inputLibraries", "library path for inputs"))
)
// boolean options.
var (
- CompressOption = flagsets.NewBoolOptionType("inputCompress", "compress option for input")
- PreserveDirOption = flagsets.NewBoolOptionType("inputPreserveDir", "preserve directory in archive for inputs")
- FollowSymlinksOption = flagsets.NewBoolOptionType("inputFollowSymlinks", "follow symbolic links during archive creation for inputs")
+ CompressOption = RegisterOption(flagsets.NewBoolOptionType("inputCompress", "compress option for input"))
+ PreserveDirOption = RegisterOption(flagsets.NewBoolOptionType("inputPreserveDir", "preserve directory in archive for inputs"))
+ FollowSymlinksOption = RegisterOption(flagsets.NewBoolOptionType("inputFollowSymlinks", "follow symbolic links during archive creation for inputs"))
)
// data options.
var (
- DataOption = flagsets.NewBytesOptionType("inputData", "data (string, !!string or !")
+ DataOption = RegisterOption(flagsets.NewBytesOptionType("inputData", "data (string, !!string or !"))
)
// yaml/json options.
var (
- YAMLOption = flagsets.NewYAMLOptionType("inputYaml", "YAML formatted text")
- JSONOption = flagsets.NewYAMLOptionType("inputJson", "JSON formatted text")
- FormattedJSONOption = flagsets.NewYAMLOptionType("inputFormattedJson", "JSON formatted text")
+ YAMLOption = RegisterOption(flagsets.NewYAMLOptionType("inputYaml", "YAML formatted text"))
+ JSONOption = RegisterOption(flagsets.NewYAMLOptionType("inputJson", "JSON formatted text"))
+ FormattedJSONOption = RegisterOption(flagsets.NewYAMLOptionType("inputFormattedJson", "JSON formatted text"))
)
var (
- ValuesOption = flagsets.NewValueMapYAMLOptionType("inputValues", "YAML based generic values for inputs")
- ComponentOption = flagsets.NewStringOptionType("inputComponent", "component name")
+ ValuesOption = RegisterOption(flagsets.NewValueMapYAMLOptionType("inputValues", "YAML based generic values for inputs"))
+ ComponentOption = RegisterOption(flagsets.NewStringOptionType("inputComponent", "component name"))
)
// RepositoryOption sets the repository or registry for an input.
-var RepositoryOption = flagsets.NewStringOptionType("inputRepository", "repository or registry for inputs")
+var RepositoryOption = RegisterOption(flagsets.NewStringOptionType("inputRepository", "repository or registry for inputs"))
diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/setup.go b/cmds/ocm/commands/ocmcmds/common/inputs/setup.go
new file mode 100644
index 0000000000..c7afdfe374
--- /dev/null
+++ b/cmds/ocm/commands/ocmcmds/common/inputs/setup.go
@@ -0,0 +1,27 @@
+package inputs
+
+import (
+ "ocm.software/ocm/api/datacontext"
+ "ocm.software/ocm/api/ocm/cpi"
+)
+
+func init() {
+ datacontext.RegisterSetupHandler(datacontext.SetupHandlerFunction(setupContext))
+}
+
+func setupContext(mode datacontext.BuilderMode, ctx datacontext.Context) {
+ if octx, ok := ctx.(cpi.Context); ok {
+ switch mode {
+ case datacontext.MODE_SHARED:
+ fallthrough
+ case datacontext.MODE_DEFAULTED:
+ // do nothing, fallback to the default attribute lookup
+ case datacontext.MODE_EXTENDED:
+ SetFor(octx, NewInputTypeScheme(nil, DefaultInputTypeScheme))
+ case datacontext.MODE_CONFIGURED:
+ SetFor(octx, DefaultInputTypeScheme.Copy())
+ case datacontext.MODE_INITIAL:
+ SetFor(octx, NewInputTypeScheme(nil))
+ }
+ }
+}
diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/binary/inputtype_test.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/binary/inputtype_test.go
index eb381a2ded..0566289430 100644
--- a/cmds/ocm/commands/ocmcmds/common/inputs/types/binary/inputtype_test.go
+++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/binary/inputtype_test.go
@@ -18,7 +18,7 @@ var _ = Describe("Input Type", func() {
It("simple string decode", func() {
env.Set(options.CompressOption, "true")
- env.Set(options.MediaTypeOption, "media")
+ env.Set(options.MediatypeOption, "media")
env.Set(options.DataOption, "!stringdata")
env.Check(&Spec{
Data: runtime.Binary("stringdata"),
@@ -28,7 +28,7 @@ var _ = Describe("Input Type", func() {
It("binary decode", func() {
env.Set(options.CompressOption, "true")
- env.Set(options.MediaTypeOption, "media")
+ env.Set(options.MediatypeOption, "media")
env.Set(options.DataOption, "IXN0cmluZ2RhdGE=")
env.Check(&Spec{
Data: runtime.Binary("!stringdata"),
diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/directory/inputtype_test.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/directory/inputtype_test.go
index 6679c7905b..bbc2aa7065 100644
--- a/cmds/ocm/commands/ocmcmds/common/inputs/types/directory/inputtype_test.go
+++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/directory/inputtype_test.go
@@ -21,7 +21,7 @@ var _ = Describe("Input Type", func() {
It("simple decode", func() {
env.Set(options.PathOption, "mypath")
env.Set(options.CompressOption, "true")
- env.Set(options.MediaTypeOption, "media")
+ env.Set(options.MediatypeOption, "media")
env.Set(options.PreserveDirOption, "false")
env.Set(options.FollowSymlinksOption, "true")
env.Set(options.IncludeOption, "x")
diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact/cli.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact/cli.go
index 043fd1f1f4..9e1d6a3e00 100644
--- a/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact/cli.go
+++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact/cli.go
@@ -6,8 +6,8 @@ import (
"ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options"
)
-func ConfigHandler() flagsets.ConfigOptionTypeSetHandler {
- return cpi.NewMediaFileSpecOptionType(TYPE, AddConfig,
+func ConfigHandler(t string) flagsets.ConfigOptionTypeSetHandler {
+ return cpi.NewMediaFileSpecOptionType(t, AddConfig,
options.PathOption, options.HintOption, options.PlatformsOption)
}
diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact/input_test.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact/input_test.go
index 27d6350d16..be706dad5d 100644
--- a/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact/input_test.go
+++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact/input_test.go
@@ -120,23 +120,24 @@ var _ = Describe("Test Environment", func() {
Context("inputs", func() {
BeforeEach(func() {
flags = &pflag.FlagSet{}
- opts = inputs.DefaultInputTypeScheme.CreateOptions()
+ opts = inputs.DefaultInputTypeScheme.CreateConfigTypeSetConfigProvider().CreateOptions()
opts.AddFlags(flags)
cfg = flagsets.Config{}
})
It("input type", func() {
fmt.Printf("input option names: %+v\n", opts.Names())
+ prov := inputs.DefaultInputTypeScheme.CreateConfigTypeSetConfigProvider()
MustBeSuccessful(flagsets.ParseOptionsFor(flags,
- flagsets.OptionSpec(inputs.DefaultInputTypeScheme.ConfigTypeSetConfigProvider().GetTypeOptionType(), me.TYPE),
+ flagsets.OptionSpec(prov.GetTypeOptionType(), me.TYPE),
flagsets.OptionSpec(options.PathOption, "ghcr.io/open-component-model/image:v1.0"),
flagsets.OptionSpec(options.PlatformsOption, "linux/amd64"),
flagsets.OptionSpec(options.PlatformsOption, "/arm64"),
))
- cfg := Must(inputs.DefaultInputTypeScheme.GetConfigFor(opts))
+ cfg := Must(prov.GetConfigFor(opts))
fmt.Printf("selected input options: %+v\n", cfg)
- spec := Must(inputs.DefaultInputTypeScheme.GetInputSpecFor(opts))
+ spec := Must(inputs.DefaultInputTypeScheme.GetInputSpecFor(cfg))
Expect(spec).To(Equal(me.New("ghcr.io/open-component-model/image:v1.0", "linux/amd64", "/arm64")))
})
})
diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact/type.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact/type.go
index 4c5ae34738..13682a9093 100644
--- a/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact/type.go
+++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact/type.go
@@ -10,8 +10,8 @@ const (
)
func init() {
- inputs.DefaultInputTypeScheme.Register(inputs.NewInputType(TYPE, &Spec{}, usage, ConfigHandler()))
- inputs.DefaultInputTypeScheme.Register(inputs.NewInputType(LEGACY_TYPE, &Spec{}, legacy_usage, ConfigHandler()))
+ inputs.DefaultInputTypeScheme.Register(inputs.NewInputType(TYPE, &Spec{}, usage, ConfigHandler(TYPE)))
+ inputs.DefaultInputTypeScheme.Register(inputs.NewInputType(LEGACY_TYPE, &Spec{}, legacy_usage, ConfigHandler(LEGACY_TYPE)))
}
const legacy_usage = `
diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin/plugin.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin/plugin.go
new file mode 100644
index 0000000000..15e49e118f
--- /dev/null
+++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin/plugin.go
@@ -0,0 +1,98 @@
+package plugin
+
+import (
+ "bytes"
+ "encoding/json"
+ "sync"
+
+ "github.com/mandelsoft/goutils/errors"
+
+ "ocm.software/ocm/api/credentials"
+ "ocm.software/ocm/api/credentials/identity/hostpath"
+ "ocm.software/ocm/api/ocm/cpi"
+ "ocm.software/ocm/api/ocm/plugin"
+ "ocm.software/ocm/api/ocm/plugin/descriptor"
+)
+
+type plug = plugin.Plugin
+
+// PluginHandler is a shared object between the GetAccess implementation and the Spec implementation. The
+// object knows the actual plugin and can therefore forward the method calls to corresponding cli commands.
+type PluginHandler struct {
+ lock sync.Mutex
+ plug
+
+ // cached info
+ access *access
+ err error
+ orig []byte
+}
+
+func NewPluginHandler(p plugin.Plugin) *PluginHandler {
+ return &PluginHandler{plug: p}
+}
+
+func (p *PluginHandler) GetAccess(spec *Spec, ctx cpi.Context) (*access, error) {
+ raw, err := spec.GetRaw()
+ if err != nil {
+ return nil, errors.Wrapf(err, "cannot marshal input specification")
+ }
+ p.lock.Lock()
+ defer p.lock.Unlock()
+
+ if p.access != nil || p.err != nil {
+ if bytes.Equal(raw, p.orig) {
+ return p.access, p.err
+ }
+ }
+ mspec := p.GetInputTypeDescriptor(spec.GetKind(), spec.GetVersion())
+ if mspec == nil {
+ return nil, errors.ErrNotFound(descriptor.KIND_INPUTTYPE, spec.GetType(), descriptor.KIND_PLUGIN, p.Name())
+ }
+
+ info, err := p.plug.ValidateInputSpec(raw)
+ p.err = err
+ if err != nil {
+ return nil, err
+ }
+ p.access = newAccess(p, spec, ctx, info)
+
+ creddata, err := p.getCredentialData(info, ctx)
+ if err != nil {
+ return nil, err
+ }
+ p.access.creds = creddata
+
+ return p.access, nil
+}
+
+func (p *PluginHandler) getCredentialData(info *plugin.InputSpecInfo, ctx cpi.Context) (json.RawMessage, error) {
+ var (
+ err error
+ creds credentials.Credentials
+ )
+
+ if len(info.ConsumerId) > 0 {
+ creds, err = credentials.CredentialsForConsumer(ctx, info.ConsumerId, hostpath.IdentityMatcher(info.ConsumerId.Type()))
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ var creddata json.RawMessage
+ if creds != nil {
+ creddata, err = json.Marshal(creds)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return creddata, nil
+}
+
+func (p *PluginHandler) Describe(spec *Spec, ctx cpi.Context) string {
+ acc, err := p.GetAccess(spec, ctx)
+ if err != nil {
+ return err.Error()
+ }
+ return acc.info.Short
+}
diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin/plugin_test.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin/plugin_test.go
new file mode 100644
index 0000000000..b237edbc86
--- /dev/null
+++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin/plugin_test.go
@@ -0,0 +1,140 @@
+package plugin_test
+
+import (
+ "os"
+
+ "github.com/mandelsoft/goutils/sliceutils"
+ . "github.com/mandelsoft/goutils/testutils"
+ "github.com/mandelsoft/goutils/transformer"
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+ "github.com/spf13/pflag"
+ "ocm.software/ocm/api/ocm/extensions/accessmethods/options"
+ . "ocm.software/ocm/api/ocm/plugin/testutils"
+ "ocm.software/ocm/api/utils/cobrautils/flagsets"
+ . "ocm.software/ocm/cmds/ocm/testhelper"
+
+ "github.com/mandelsoft/filepath/pkg/filepath"
+
+ "ocm.software/ocm/api/ocm/extensions/attrs/plugincacheattr"
+ "ocm.software/ocm/api/ocm/plugin/plugins"
+ "ocm.software/ocm/api/utils/runtime"
+ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs"
+ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin"
+)
+
+const PLUGIN = "input"
+
+const TESTDATA = "this is some test data\n"
+
+var _ = Describe("Input Command Test Environment", func() {
+ Context("plugin execution", func() {
+ var env *TestEnv
+ var plugindir TempPluginDir
+ var registry plugins.Set
+
+ BeforeEach(func() {
+ env = NewTestEnv(TestData())
+ plugindir = Must(ConfigureTestPlugins(env, "testdata/plugins"))
+ registry = plugincacheattr.Get(env)
+ })
+
+ AfterEach(func() {
+ plugindir.Cleanup()
+ env.Cleanup()
+ })
+
+ It("loads plugin", func() {
+ // Expect(registration.RegisterExtensions(env)).To(Succeed())
+ p := registry.Get(PLUGIN)
+ Expect(p).NotTo(BeNil())
+ Expect(p.Error()).To(Equal(""))
+ })
+
+ It("gets blob", func() {
+ p := registry.Get(PLUGIN)
+ t := plugin.NewType("demo", p, &p.GetDescriptor().Inputs[0])
+
+ file := Must(os.CreateTemp("", "input*"))
+ defer os.Remove(file.Name())
+ Must(file.Write([]byte(TESTDATA)))
+ file.Close()
+ spec := `
+type: demo
+path: ` + filepath.Base(file.Name()) + `
+mediaType: plain/text
+`
+ is := Must(t.Decode([]byte(spec), runtime.DefaultYAMLEncoding))
+ Expect(is.GetType()).To(Equal("demo"))
+
+ ctx := inputs.NewContext(env.CLIContext(), nil, nil)
+ blob, hint := Must2(is.GetBlob(ctx, inputs.InputResourceInfo{}))
+ defer blob.Close()
+ Expect(hint).To(Equal(filepath.Base(file.Name())))
+ data := Must(blob.Get())
+ Expect(string(data)).To(Equal(TESTDATA))
+ })
+
+ It("gets input", func() {
+ scheme := inputs.For(env.CLIContext())
+ plugin.RegisterPlugins(env.CLIContext())
+
+ file := Must(os.CreateTemp("", "input*"))
+ defer os.Remove(file.Name())
+ Must(file.Write([]byte(TESTDATA)))
+ file.Close()
+ spec := `
+type: demo
+path: ` + filepath.Base(file.Name()) + `
+mediaType: plain/text
+`
+
+ is := Must(scheme.DecodeInputSpec([]byte(spec), runtime.DefaultYAMLEncoding))
+ Expect(is.GetType()).To(Equal("demo"))
+
+ ctx := inputs.NewContext(env.CLIContext(), nil, nil)
+ blob, hint := Must2(is.GetBlob(ctx, inputs.InputResourceInfo{}))
+ defer blob.Close()
+ Expect(hint).To(Equal(filepath.Base(file.Name())))
+ data := Must(blob.Get())
+ Expect(string(data)).To(Equal(TESTDATA))
+ })
+
+ It("handles input options", func() {
+ scheme := inputs.For(env.CLIContext())
+ plugin.RegisterPlugins(env.CLIContext())
+
+ it := scheme.GetInputType("demo")
+ Expect(it).NotTo(BeNil())
+
+ h := it.ConfigOptionTypeSetHandler()
+ Expect(h).NotTo(BeNil())
+ Expect(h.GetName()).To(Equal("demo"))
+
+ ot := h.OptionTypes()
+ Expect(len(ot)).To(Equal(2))
+
+ opts := h.CreateOptions()
+ Expect(sliceutils.Transform(opts.Options(), transformer.GetName[flagsets.Option, string])).To(ConsistOf(
+ "mediaType", "inputPath"))
+
+ fs := &pflag.FlagSet{}
+ fs.SortFlags = true
+ opts.AddFlags(fs)
+
+ Expect("\n" + fs.FlagUsages()).To(Equal(`
+ --inputPath string path field for input
+ --mediaType string media type for artifact blob representation
+`))
+
+ MustBeSuccessful(fs.Parse([]string{"--inputPath", "filepath", "--" + options.MediatypeOption.GetName(), "yaml"}))
+
+ cfg := flagsets.Config{}
+ MustBeSuccessful(h.ApplyConfig(opts, cfg))
+ Expect(cfg).To(YAMLEqual(`
+mediaType: yaml
+path: filepath
+`))
+ })
+ })
+})
diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin/registration.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin/registration.go
new file mode 100644
index 0000000000..437ca30c78
--- /dev/null
+++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin/registration.go
@@ -0,0 +1,25 @@
+package plugin
+
+import (
+ "github.com/mandelsoft/goutils/generics"
+
+ clictx "ocm.software/ocm/api/cli"
+ "ocm.software/ocm/api/ocm/extensions/attrs/plugincacheattr"
+ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs"
+)
+
+func RegisterPlugins(ctx clictx.Context) {
+ scheme := inputs.For(ctx)
+
+ plugins := plugincacheattr.Get(ctx)
+ for _, n := range plugins.PluginNames() {
+ p := plugins.Get(n)
+ if !p.IsValid() {
+ continue
+ }
+ for _, d := range p.GetDescriptor().Inputs {
+ t := NewType(d.Name, p, generics.Pointer(d))
+ scheme.Register(t)
+ }
+ }
+}
diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin/spec.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin/spec.go
new file mode 100644
index 0000000000..4f62546214
--- /dev/null
+++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin/spec.go
@@ -0,0 +1,101 @@
+package plugin
+
+import (
+ "encoding/json"
+
+ "github.com/mandelsoft/goutils/errors"
+ "k8s.io/apimachinery/pkg/util/validation/field"
+
+ "ocm.software/ocm/api/credentials"
+ "ocm.software/ocm/api/credentials/identity/hostpath"
+ "ocm.software/ocm/api/ocm"
+ cpi "ocm.software/ocm/api/ocm/cpi/accspeccpi"
+ "ocm.software/ocm/api/ocm/plugin"
+ "ocm.software/ocm/api/ocm/plugin/ppi"
+ "ocm.software/ocm/api/utils/accessobj"
+ "ocm.software/ocm/api/utils/blobaccess/blobaccess"
+ "ocm.software/ocm/api/utils/runtime"
+ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs"
+)
+
+type Spec struct {
+ runtime.UnstructuredVersionedTypedObject `json:",inline"`
+ handler *PluginHandler
+}
+
+var _ inputs.InputSpec = &Spec{}
+
+func (s *Spec) Validate(fldPath *field.Path, ctx inputs.Context, inputFilePath string) field.ErrorList {
+ _, err := s.handler.GetAccess(s, ctx.OCMContext())
+ if err != nil {
+ return field.ErrorList{field.Invalid(fldPath, nil, err.Error())}
+ }
+ return nil
+}
+
+func (s *Spec) GetBlob(ctx inputs.Context, info inputs.InputResourceInfo) (blobaccess.BlobAccess, string, error) {
+ acc, err := s.handler.GetAccess(s, ctx.OCMContext())
+ if err != nil {
+ return nil, "", err
+ }
+ dir, err := inputs.GetBaseDir(ctx.FileSystem(), info.InputFilePath)
+ if err != nil {
+ dir = "."
+ }
+ return acc.GetBlob(dir)
+}
+
+func (s *Spec) GetInputVersion(ctx inputs.Context) string {
+ return ""
+}
+
+func (s *Spec) Describe(ctx cpi.Context) string {
+ return s.handler.Describe(s, ctx)
+}
+
+func (s *Spec) Handler() *PluginHandler {
+ return s.handler
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+type access struct {
+ ctx ocm.Context
+
+ handler *PluginHandler
+ spec *Spec
+ info *ppi.InputSpecInfo
+ creds json.RawMessage
+}
+
+func newAccess(p *PluginHandler, spec *Spec, ctx ocm.Context, info *ppi.InputSpecInfo) *access {
+ return &access{
+ ctx: ctx,
+ handler: p,
+ spec: spec,
+ info: info,
+ }
+}
+
+func (m *access) MimeType() string {
+ return m.info.MediaType
+}
+
+func (m *access) GetBlob(dir string) (blobaccess.BlobAccess, string, error) {
+ spec, err := json.Marshal(m.spec)
+ if err != nil {
+ return nil, "", errors.Wrapf(err, "cannot marshal input spec")
+ }
+ return accessobj.CachedBlobAccessForWriter(m.ctx, m.MimeType(), plugin.NewInputDataWriter(m.handler.plug, dir, m.creds, spec)), m.info.Hint, nil
+}
+
+func (m *access) GetConsumerId(uctx ...credentials.UsageContext) credentials.ConsumerIdentity {
+ if len(m.info.ConsumerId) == 0 {
+ return nil
+ }
+ return m.info.ConsumerId
+}
+
+func (m *access) GetIdentityMatcher() string {
+ return hostpath.IDENTITY_TYPE
+}
diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin/suite_test.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin/suite_test.go
new file mode 100644
index 0000000000..8c8dfdc441
--- /dev/null
+++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin/suite_test.go
@@ -0,0 +1,13 @@
+package plugin_test
+
+import (
+ "testing"
+
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+)
+
+func TestConfig(t *testing.T) {
+ RegisterFailHandler(Fail)
+ RunSpecs(t, "Input Plugin Handler Test Suite")
+}
diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin/testdata/plugin/app/app.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin/testdata/plugin/app/app.go
new file mode 100644
index 0000000000..2e39d1ecef
--- /dev/null
+++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin/testdata/plugin/app/app.go
@@ -0,0 +1,29 @@
+package app
+
+import (
+ "ocm.software/ocm/api/ocm/plugin/ppi"
+ "ocm.software/ocm/api/ocm/plugin/ppi/cmds"
+ "ocm.software/ocm/api/version"
+ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin/testdata/plugin/inputhandlers"
+)
+
+func New() (ppi.Plugin, error) {
+ p := ppi.NewPlugin("input", version.Get().String())
+
+ p.SetShort("fake input plugin")
+ p.SetLong("providing fake input")
+
+ err := p.RegisterInputType(inputhandlers.New())
+ if err != nil {
+ return nil, err
+ }
+ return p, nil
+}
+
+func Run(args []string, opts ...cmds.Option) error {
+ p, err := New()
+ if err != nil {
+ return err
+ }
+ return cmds.NewPluginCommand(p, opts...).Execute(args)
+}
diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin/testdata/plugin/inputhandlers/demo.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin/testdata/plugin/inputhandlers/demo.go
new file mode 100644
index 0000000000..083199dd35
--- /dev/null
+++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin/testdata/plugin/inputhandlers/demo.go
@@ -0,0 +1,103 @@
+package inputhandlers
+
+import (
+ out "fmt"
+ "io"
+ "os"
+ "strings"
+
+ "github.com/mandelsoft/filepath/pkg/filepath"
+ "github.com/mandelsoft/goutils/errors"
+
+ "ocm.software/ocm/api/credentials"
+ "ocm.software/ocm/api/credentials/cpi"
+ "ocm.software/ocm/api/ocm/plugin/ppi"
+ "ocm.software/ocm/api/tech/oci/identity"
+ "ocm.software/ocm/api/utils/cobrautils/flagsets"
+ "ocm.software/ocm/api/utils/runtime"
+ "ocm.software/ocm/cmds/demoplugin/common"
+ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options"
+)
+
+const (
+ NAME = "demo"
+ VERSION = "v1"
+)
+
+type InputSpec struct {
+ runtime.ObjectVersionedType `json:",inline"`
+
+ Path string `json:"path"`
+ MediaType string `json:"mediaType,omitempty"`
+}
+
+type InputType struct {
+ ppi.InputTypeBase
+}
+
+var _ ppi.InputType = (*InputType)(nil)
+
+func New() ppi.InputType {
+ return &InputType{
+ InputTypeBase: ppi.MustNewInputTypeBase(NAME, &InputSpec{}, "demo access to temp files", ""),
+ }
+}
+
+func (a *InputType) Options() []flagsets.ConfigOptionType {
+ return []flagsets.ConfigOptionType{
+ options.MediatypeOption,
+ options.PathOption,
+ }
+}
+
+func (a *InputType) Decode(data []byte, unmarshaler runtime.Unmarshaler) (runtime.TypedObject, error) {
+ if unmarshaler == nil {
+ unmarshaler = runtime.DefaultYAMLEncoding
+ }
+ var spec InputSpec
+ err := unmarshaler.Unmarshal(data, &spec)
+ if err != nil {
+ return nil, err
+ }
+ return &spec, nil
+}
+
+func (a *InputType) ValidateSpecification(p ppi.Plugin, dir string, spec ppi.InputSpec) (*ppi.InputSpecInfo, error) {
+ var info ppi.InputSpecInfo
+
+ my := spec.(*InputSpec)
+
+ 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{
+ cpi.ID_TYPE: common.CONSUMER_TYPE,
+ identity.ID_HOSTNAME: "localhost",
+ identity.ID_PATHPREFIX: my.Path,
+ }
+ info.Short = "temp file " + my.Path
+ info.Hint = my.Path
+
+ return &info, nil
+}
+
+func (a *InputType) ComposeSpecification(p ppi.Plugin, opts ppi.Config, config ppi.Config) error {
+ list := errors.ErrListf("configuring options")
+ list.Add(flagsets.AddFieldByOptionP(opts, options.PathOption, config, "path"))
+ list.Add(flagsets.AddFieldByOptionP(opts, options.MediatypeOption, config, "mediaType"))
+ return list.Result()
+}
+
+func (a *InputType) Reader(p ppi.Plugin, dir string, spec ppi.InputSpec, creds credentials.Credentials) (io.ReadCloser, error) {
+ my := spec.(*InputSpec)
+
+ root := os.TempDir()
+ return os.Open(filepath.Join(root, my.Path))
+}
diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin/testdata/plugin/main.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin/testdata/plugin/main.go
new file mode 100644
index 0000000000..3d9d08bd8d
--- /dev/null
+++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin/testdata/plugin/main.go
@@ -0,0 +1,14 @@
+package main
+
+import (
+ "os"
+
+ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin/testdata/plugin/app"
+)
+
+func main() {
+ err := app.Run(os.Args[1:])
+ if err != nil {
+ os.Exit(1)
+ }
+}
diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin/testdata/plugins/input b/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin/testdata/plugins/input
new file mode 100755
index 0000000000..97d52eddbb
--- /dev/null
+++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin/testdata/plugins/input
@@ -0,0 +1,2 @@
+#!/bin/bash
+go run testdata/plugin/main.go "$@"
\ No newline at end of file
diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin/type.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin/type.go
new file mode 100644
index 0000000000..732e057387
--- /dev/null
+++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/plugin/type.go
@@ -0,0 +1,68 @@
+package plugin
+
+import (
+ "ocm.software/ocm/api/ocm/plugin"
+ "ocm.software/ocm/api/utils/cobrautils/flagsets"
+ "ocm.software/ocm/api/utils/runtime"
+ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs"
+ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options"
+)
+
+type inputType struct {
+ inputs.InputType
+ plug plugin.Plugin
+ cliopts flagsets.ConfigOptionTypeSet
+}
+
+var _ inputs.InputType = (*inputType)(nil)
+
+func NewType(name string, p plugin.Plugin, desc *plugin.InputTypeDescriptor) inputs.InputType {
+ t := &inputType{
+ plug: p,
+ }
+
+ cfghdlr := flagsets.NewConfigOptionTypeSetHandler(name, t.AddConfig)
+ for _, o := range desc.CLIOptions {
+ var opt flagsets.ConfigOptionType
+ if o.Type == "" {
+ opt = options.DefaultRegistry.GetOptionType(o.Name)
+ if opt == nil {
+ p.Context().Logger(plugin.TAG).Warn("unknown option", "plugin", p.Name(), "inputtype", name, "option", o.Name)
+ }
+ } else {
+ var err error
+ opt, err = options.DefaultRegistry.CreateOptionType(o.Type, o.Name, o.Description)
+ if err != nil {
+ p.Context().Logger(plugin.TAG).Warn("invalid option", "plugin", p.Name(), "inputtype", name, "option", o.Name, "error", err.Error())
+ }
+ }
+ if opt != nil {
+ cfghdlr.AddOptionType(opt)
+ }
+ }
+ if cfghdlr.Size() > 0 {
+ t.cliopts = cfghdlr
+ }
+
+ usage := desc.Description
+ format := desc.Format
+ if format != "" {
+ usage += "\n" + format
+ }
+ t.InputType = inputs.NewInputType(name, &Spec{}, usage, cfghdlr)
+ return t
+}
+
+func (t *inputType) Decode(data []byte, unmarshaler runtime.Unmarshaler) (inputs.InputSpec, error) {
+ spec, err := t.InputType.Decode(data, unmarshaler)
+ if err != nil {
+ return nil, err
+ }
+ spec.(*Spec).handler = NewPluginHandler(t.plug)
+ return spec, nil
+}
+
+func (t *inputType) AddConfig(opts flagsets.ConfigOptions, cfg flagsets.Config) error {
+ opts = opts.FilterBy(t.cliopts.HasOptionType)
+ return t.plug.ComposeInputSpec(t.GetType(), opts, cfg)
+}
diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/utf8/inputtype_test.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/utf8/inputtype_test.go
index 1afad536f4..8feb3f38b9 100644
--- a/cmds/ocm/commands/ocmcmds/common/inputs/types/utf8/inputtype_test.go
+++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/utf8/inputtype_test.go
@@ -22,7 +22,7 @@ var _ = Describe("Input Type", func() {
It("simple string decode", func() {
env.Set(options.CompressOption, "true")
- env.Set(options.MediaTypeOption, "media")
+ env.Set(options.MediatypeOption, "media")
env.Set(options.TextOption, "stringdata")
env.Check(&Spec{
Text: "stringdata",
@@ -32,7 +32,7 @@ var _ = Describe("Input Type", func() {
It("simple json decode", func() {
env.Set(options.CompressOption, "true")
- env.Set(options.MediaTypeOption, "media")
+ env.Set(options.MediatypeOption, "media")
env.Set(options.JSONOption, `field: value`)
env.Check(&Spec{
Json: []byte(`{"field":"value"}`),
@@ -42,7 +42,7 @@ var _ = Describe("Input Type", func() {
It("simple formatted json decode", func() {
env.Set(options.CompressOption, "true")
- env.Set(options.MediaTypeOption, "media")
+ env.Set(options.MediatypeOption, "media")
env.Set(options.FormattedJSONOption, `field: value`)
env.Check(&Spec{
FormattedJson: []byte(`{"field":"value"}`),
@@ -52,7 +52,7 @@ var _ = Describe("Input Type", func() {
It("simple yaml decode", func() {
env.Set(options.CompressOption, "true")
- env.Set(options.MediaTypeOption, "media")
+ env.Set(options.MediatypeOption, "media")
env.Set(options.YAMLOption, `field: value`)
env.Check(&Spec{
Yaml: []byte(`{"field":"value"}`),
diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/wget/cli.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/wget/cli.go
index e8e16f098f..73cb7a1bc8 100644
--- a/cmds/ocm/commands/ocmcmds/common/inputs/types/wget/cli.go
+++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/wget/cli.go
@@ -9,7 +9,7 @@ func ConfigHandler() flagsets.ConfigOptionTypeSetHandler {
return flagsets.NewConfigOptionTypeSetHandler(
TYPE, AddConfig,
options.URLOption,
- options.MediaTypeOption,
+ options.MediatypeOption,
options.HTTPHeaderOption,
options.HTTPVerbOption,
options.HTTPBodyOption,
@@ -19,7 +19,7 @@ func ConfigHandler() flagsets.ConfigOptionTypeSetHandler {
func AddConfig(opts flagsets.ConfigOptions, config flagsets.Config) error {
flagsets.AddFieldByOptionP(opts, options.URLOption, config, "url")
- flagsets.AddFieldByOptionP(opts, options.MediaTypeOption, config, "mediaType")
+ flagsets.AddFieldByOptionP(opts, options.MediatypeOption, config, "mediaType")
flagsets.AddFieldByOptionP(opts, options.HTTPHeaderOption, config, "header")
flagsets.AddFieldByOptionP(opts, options.HTTPVerbOption, config, "verb")
flagsets.AddFieldByOptionP(opts, options.HTTPBodyOption, config, "body")
diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/wget/input_test.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/wget/input_test.go
index 6c20995db8..17c1a2f415 100644
--- a/cmds/ocm/commands/ocmcmds/common/inputs/types/wget/input_test.go
+++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/wget/input_test.go
@@ -21,7 +21,7 @@ var _ = Describe("Input Type", func() {
It("simple decode", func() {
env.Set(options.URLOption, "https://example.com/test")
- env.Set(options.MediaTypeOption, mime.MIME_TEXT)
+ env.Set(options.MediatypeOption, mime.MIME_TEXT)
env.Set(options.HTTPHeaderOption, "Host: developer.mozilla.org")
env.Set(options.HTTPHeaderOption, "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 Firefox/50.0")
env.Set(options.HTTPHeaderOption, "Accept: text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8")
diff --git a/cmds/ocm/commands/ocmcmds/common/options/scriptoption/option.go b/cmds/ocm/commands/ocmcmds/common/options/scriptoption/option.go
index ff8acf3c7d..c9b2111314 100644
--- a/cmds/ocm/commands/ocmcmds/common/options/scriptoption/option.go
+++ b/cmds/ocm/commands/ocmcmds/common/options/scriptoption/option.go
@@ -109,5 +109,8 @@ func (o *Option) ApplyTransferOption(opts transferhandler.TransferOptions) error
} else if o.ScriptFile != "" {
err = spiff.ScriptByFile(o.ScriptFile, o.FileSystem).ApplyTransferOption(opts)
}
+ if err == nil && o.FileSystem != nil && (o.ScriptData != nil || o.ScriptFile != "") {
+ err = spiff.ScriptFilesystem(o.FileSystem).ApplyTransferOption(opts)
+ }
return err
}
diff --git a/cmds/ocm/commands/ocmcmds/common/options/transferhandleroption/option.go b/cmds/ocm/commands/ocmcmds/common/options/transferhandleroption/option.go
new file mode 100644
index 0000000000..1f635002a6
--- /dev/null
+++ b/cmds/ocm/commands/ocmcmds/common/options/transferhandleroption/option.go
@@ -0,0 +1,74 @@
+package transferhandleroption
+
+import (
+ "strings"
+
+ "github.com/spf13/pflag"
+
+ clictx "ocm.software/ocm/api/cli"
+ "ocm.software/ocm/api/datacontext/attrs/vfsattr"
+ "ocm.software/ocm/api/ocm"
+ "ocm.software/ocm/api/ocm/tools/transfer/transferhandler"
+ "ocm.software/ocm/api/utils"
+ "ocm.software/ocm/cmds/ocm/common/options"
+)
+
+func From(o options.OptionSetProvider) *Option {
+ var opt *Option
+ o.AsOptionSet().Get(&opt)
+ return opt
+}
+
+func New() *Option {
+ return &Option{}
+}
+
+type Option struct {
+ setting string
+ Path string
+ Config []byte
+}
+
+var (
+ _ options.OptionWithCLIContextCompleter = (*Option)(nil)
+ _ transferhandler.TransferOption = (*Option)(nil)
+)
+
+func (o *Option) AddFlags(fs *pflag.FlagSet) {
+ fs.StringVarP(&o.setting, "transfer-handler", "T", "", "transfer handler ([=)")
+}
+
+func (o *Option) Configure(ctx clictx.Context) error {
+ if o.setting == "" {
+ return nil
+ }
+ idx := strings.Index(o.setting, "=")
+ if idx >= 0 {
+ o.Path = o.setting[:idx]
+ data, err := utils.ResolveData(o.setting[idx+1:], vfsattr.Get(ctx))
+ if err != nil {
+ return err
+ }
+ o.Config = data
+ } else {
+ o.Path = o.setting
+ }
+ return nil
+}
+
+func (o *Option) Usage() string {
+ s := `
+It is possible to use dedicated transfer handlers, either built-in ones or
+plugin based handlers. The option --transferHandler can be used to specify
+this handler using the hierarchical handler notation scheme.
+` + transferhandler.Usage(ocm.DefaultContext())
+ return s
+}
+
+func (o *Option) ApplyTransferOption(opts transferhandler.TransferOptions) error {
+ var err error
+ if len(o.Config) != 0 {
+ err = transferhandler.WithConfig(o.Config).ApplyTransferOption(opts)
+ }
+ return err
+}
diff --git a/cmds/ocm/commands/ocmcmds/common/resources.go b/cmds/ocm/commands/ocmcmds/common/resources.go
index 63c1d63c44..035dba5bff 100644
--- a/cmds/ocm/commands/ocmcmds/common/resources.go
+++ b/cmds/ocm/commands/ocmcmds/common/resources.go
@@ -266,7 +266,7 @@ func (a *ContentResourceSpecificationsProvider) AddFlags(fs *pflag.FlagSet) {
a.ElementMetaDataSpecificationsProvider.AddFlags(fs)
a.accprov = a.ctx.OCMContext().AccessMethods().CreateConfigTypeSetConfigProvider()
- inptypes := inputs.For(a.ctx).ConfigTypeSetConfigProvider()
+ inptypes := inputs.For(a.ctx).CreateConfigTypeSetConfigProvider()
set := flagsets.NewConfigOptionTypeSet("resources")
set.AddAll(a.accprov)
@@ -301,7 +301,7 @@ func (a *ContentResourceSpecificationsProvider) Complete() error {
unique := a.options.FilterBy(flagsets.Not(a.shared.HasOptionType))
aopts := unique.FilterBy(a.accprov.HasOptionType)
- iopts := unique.FilterBy(inputs.For(a.ctx).ConfigTypeSetConfigProvider().HasOptionType)
+ iopts := unique.FilterBy(inputs.For(a.ctx).CreateConfigTypeSetConfigProvider().HasOptionType)
if !a.options.Changed(a.contentFlags...) {
return fmt.Errorf("one of %v is required", flagsets.AddPrefix("--", a.contentFlags...))
@@ -350,7 +350,7 @@ func (a *ContentResourceSpecificationsProvider) Get() (string, error) {
if err != nil {
return "", err
}
- err = a.apply(inputs.For(a.ctx).ConfigTypeSetConfigProvider(), data)
+ err = a.apply(inputs.For(a.ctx).CreateConfigTypeSetConfigProvider(), data)
if err != nil {
return "", err
}
diff --git a/cmds/ocm/commands/ocmcmds/components/transfer/cmd.go b/cmds/ocm/commands/ocmcmds/components/transfer/cmd.go
index d01c477bf9..2cfce117e0 100644
--- a/cmds/ocm/commands/ocmcmds/components/transfer/cmd.go
+++ b/cmds/ocm/commands/ocmcmds/components/transfer/cmd.go
@@ -14,7 +14,6 @@ import (
"ocm.software/ocm/api/ocm"
"ocm.software/ocm/api/ocm/tools/transfer"
"ocm.software/ocm/api/ocm/tools/transfer/transferhandler"
- "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/spiff"
common "ocm.software/ocm/api/utils/misc"
"ocm.software/ocm/api/utils/out"
"ocm.software/ocm/cmds/ocm/commands/common/options/closureoption"
@@ -30,11 +29,11 @@ import (
"ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/skipupdateoption"
"ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/srcbyvalueoption"
"ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/stoponexistingoption"
+ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/transferhandleroption"
"ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/uploaderoption"
"ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption"
"ocm.software/ocm/cmds/ocm/commands/ocmcmds/names"
"ocm.software/ocm/cmds/ocm/commands/verbs"
- "ocm.software/ocm/cmds/ocm/common/options"
"ocm.software/ocm/cmds/ocm/common/output"
"ocm.software/ocm/cmds/ocm/common/utils"
)
@@ -69,6 +68,7 @@ func NewCommand(ctx clictx.Context, names ...string) *cobra.Command {
stoponexistingoption.New(),
uploaderoption.New(ctx.OCMContext()),
scriptoption.New(),
+ transferhandleroption.New(),
)}, utils.Names(Names, names...)...)
}
@@ -103,7 +103,8 @@ func (o *Command) Complete(args []string) error {
return fmt.Errorf("a repository or at least one argument that defines the reference is required")
}
o.TargetName = args[len(args)-1]
- return nil
+
+ return ValidateHandler(o)
}
func (o *Command) Run() error {
@@ -130,13 +131,7 @@ func (o *Command) Run() error {
return err
}
- transferopts := &spiff.Options{}
- transferhandler.From(o.ConfigContext(), transferopts)
- transferhandler.ApplyOptions(transferopts, append(options.FindOptions[transferhandler.TransferOption](o),
- spiff.Script(scriptoption.From(o).ScriptData),
- spiff.ScriptFilesystem(o.FileSystem()),
- )...)
- thdlr, err := spiff.New(transferopts)
+ thdlr, err := DetermineTransferHandler(o.Context, o)
if err != nil {
return err
}
diff --git a/cmds/ocm/commands/ocmcmds/components/transfer/cmd_test.go b/cmds/ocm/commands/ocmcmds/components/transfer/cmd_test.go
index 51948a7ad2..b0ccbc0a0f 100644
--- a/cmds/ocm/commands/ocmcmds/components/transfer/cmd_test.go
+++ b/cmds/ocm/commands/ocmcmds/components/transfer/cmd_test.go
@@ -3,11 +3,14 @@ package transfer_test
import (
"bytes"
"encoding/json"
+ "fmt"
. "github.com/mandelsoft/goutils/testutils"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
. "ocm.software/ocm/api/oci/testhelper"
+ "ocm.software/ocm/api/ocm/extensions/attrs/plugincacheattr"
+ . "ocm.software/ocm/api/ocm/plugin/testutils"
. "ocm.software/ocm/cmds/ocm/testhelper"
"github.com/spf13/cobra"
@@ -22,7 +25,7 @@ import (
"ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact"
resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes"
ctfocm "ocm.software/ocm/api/ocm/extensions/repositories/ctf"
- ocmutils "ocm.software/ocm/api/ocm/ocmutils"
+ "ocm.software/ocm/api/ocm/ocmutils"
handlercfg "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/config"
"ocm.software/ocm/api/utils"
"ocm.software/ocm/api/utils/accessio"
@@ -42,6 +45,11 @@ const (
OCIHOST = "alias"
)
+const (
+ PLUGIN = "transferplugin"
+ HANDLER = "demo"
+)
+
func CheckComponentInArchive(env *TestEnv, ldesc *artdesc.Descriptor, out string) {
tgt, err := ctfocm.Open(env.OCMContext(), accessobj.ACC_READONLY, out, 0, accessio.PathFileSystem(env.FileSystem()))
Expect(err).To(Succeed())
@@ -94,7 +102,7 @@ var _ = Describe("Test Environment", func() {
_ = ldesc
BeforeEach(func() {
- env = NewTestEnv()
+ env = NewTestEnv(TestData())
FakeOCIRepo(env.Builder, OCIPATH, OCIHOST)
@@ -114,7 +122,6 @@ var _ = Describe("Test Environment", func() {
env.Access(
ociartifact.New(oci.StandardOCIRef(OCIHOST+".alias", OCINAMESPACE, OCIVERSION)),
)
- env.Label("transportByValue", true)
})
env.Resource("ref", "", resourcetypes.OCI_IMAGE, metav1.LocalRelation, func() {
env.Access(
@@ -158,6 +165,23 @@ transferring version "github.com/mandelsoft/test:v1"...
CheckComponentInArchive(env, ldesc, OUT)
})
+ It("transfers ctf with named handler", func() {
+ buf := bytes.NewBuffer(nil)
+ Expect(env.CatchOutput(buf).Execute("transfer", "components", "--copy-resources", "--transfer-handler", "ocm/standard", ARCH, ARCH, OUT)).To(Succeed())
+ Expect(buf.String()).To(StringEqualTrimmedWithContext(`
+using transfer handler ocm/standard
+transferring version "github.com/mandelsoft/test:v1"...
+...resource 0 testdata[plainText]...
+...resource 1 value[ociImage](ocm/value:v2.0)...
+...resource 2 ref[ociImage](ocm/ref:v2.0)...
+...adding component version...
+1 versions transferred
+`))
+
+ Expect(env.DirExists(OUT)).To(BeTrue())
+ CheckComponentInArchive(env, ldesc, OUT)
+ })
+
It("transfers ctf with --closure --lookup", func() {
buf := bytes.NewBuffer(nil)
Expect(env.CatchOutput(buf).Execute("transfer", "components", "--copy-resources", "--recursive", "--lookup", ARCH, ARCH2, ARCH2, OUT)).To(Succeed())
@@ -303,4 +327,80 @@ transferring version "github.com/mandelsoft/test:v1"...
Expect(env.FileExists(OUT)).To(BeTrue())
CheckComponentInArchive(env, ldesc, OUT)
})
+
+ Context("plugin execution", func() {
+ var plugins TempPluginDir
+
+ BeforeEach(func() {
+ plugins = Must(ConfigureTestPlugins(env, "testdata/plugins"))
+ })
+
+ AfterEach(func() {
+ plugins.Cleanup()
+ })
+
+ It("loads plugin", func() {
+ registry := plugincacheattr.Get(env)
+ // Expect(registration.RegisterExtensions(env)).To(Succeed())
+ p := registry.Get(PLUGIN)
+ Expect(p).NotTo(BeNil())
+ Expect(p.Error()).To(Equal(""))
+ })
+
+ It("transfers with copy", func() {
+ buf := bytes.NewBuffer(nil)
+ Expect(env.CatchOutput(buf).Execute("transfer", "components",
+ "--transfer-handler",
+ fmt.Sprintf("plugin/%s/%s=@testdata/config", PLUGIN, HANDLER),
+ ARCH, ARCH, OUT)).To(Succeed())
+ Expect(buf.String()).To(StringEqualTrimmedWithContext(`
+using transfer handler plugin/transferplugin/demo
+transferring version "github.com/mandelsoft/test:v1"...
+...resource 0 testdata[plainText]...
+...resource 1 value[ociImage](ocm/value:v2.0)...
+...resource 2 ref[ociImage](ocm/ref:v2.0)...
+...adding component version...
+1 versions transferred
+`))
+ Expect(env.DirExists(OUT)).To(BeTrue())
+ CheckComponentInArchive(env, ldesc, OUT)
+ })
+
+ It("transfers with copy with implicitly detected handler", func() {
+ buf := bytes.NewBuffer(nil)
+ Expect(env.CatchOutput(buf).Execute("transfer", "components",
+ "--transfer-handler",
+ fmt.Sprintf("plugin/%s=@testdata/config", PLUGIN),
+ ARCH, ARCH, OUT)).To(Succeed())
+ Expect(buf.String()).To(StringEqualTrimmedWithContext(`
+using transfer handler plugin/transferplugin
+transferring version "github.com/mandelsoft/test:v1"...
+...resource 0 testdata[plainText]...
+...resource 1 value[ociImage](ocm/value:v2.0)...
+...resource 2 ref[ociImage](ocm/ref:v2.0)...
+...adding component version...
+1 versions transferred
+`))
+ Expect(env.DirExists(OUT)).To(BeTrue())
+ CheckComponentInArchive(env, ldesc, OUT)
+ })
+
+ It("transfers without copy", func() {
+ buf := bytes.NewBuffer(nil)
+ Expect(env.CatchOutput(buf).Execute("transfer", "components",
+ "--transfer-handler",
+ fmt.Sprintf("plugin/%s/%s=@testdata/config2", PLUGIN, HANDLER),
+ ARCH, ARCH, OUT)).To(Succeed())
+ Expect(buf.String()).To(StringEqualTrimmedWithContext(`
+using transfer handler plugin/transferplugin/demo
+transferring version "github.com/mandelsoft/test:v1"...
+...resource 0 testdata[plainText]...
+...adding component version...
+1 versions transferred
+`))
+ Expect(env.DirExists(OUT)).To(BeTrue())
+ })
+
+ })
+
})
diff --git a/cmds/ocm/commands/ocmcmds/components/transfer/testdata/config b/cmds/ocm/commands/ocmcmds/components/transfer/testdata/config
new file mode 100644
index 0000000000..790de3c902
--- /dev/null
+++ b/cmds/ocm/commands/ocmcmds/components/transfer/testdata/config
@@ -0,0 +1,5 @@
+
+transferRepositories:
+ types:
+ ociArtifact:
+ - alias.alias
\ No newline at end of file
diff --git a/cmds/ocm/commands/ocmcmds/components/transfer/testdata/config2 b/cmds/ocm/commands/ocmcmds/components/transfer/testdata/config2
new file mode 100644
index 0000000000..a511ae3d2d
--- /dev/null
+++ b/cmds/ocm/commands/ocmcmds/components/transfer/testdata/config2
@@ -0,0 +1,4 @@
+transferRepositories:
+ types:
+ ociArtifact:
+ - ghcr.io
\ No newline at end of file
diff --git a/cmds/ocm/commands/ocmcmds/components/transfer/testdata/plugins/transferplugin b/cmds/ocm/commands/ocmcmds/components/transfer/testdata/plugins/transferplugin
new file mode 100755
index 0000000000..a07d800501
--- /dev/null
+++ b/cmds/ocm/commands/ocmcmds/components/transfer/testdata/plugins/transferplugin
@@ -0,0 +1,2 @@
+#!/bin/bash
+go run ../../../../../transferplugin/main.go "$@"
\ No newline at end of file
diff --git a/cmds/ocm/commands/ocmcmds/components/transfer/upload_test.go b/cmds/ocm/commands/ocmcmds/components/transfer/upload_test.go
index 494742bc50..e266853d0b 100644
--- a/cmds/ocm/commands/ocmcmds/components/transfer/upload_test.go
+++ b/cmds/ocm/commands/ocmcmds/components/transfer/upload_test.go
@@ -1,3 +1,5 @@
+//go:build unix
+
package transfer_test
import (
diff --git a/cmds/ocm/commands/ocmcmds/components/transfer/utils.go b/cmds/ocm/commands/ocmcmds/components/transfer/utils.go
new file mode 100644
index 0000000000..1a0cc2118b
--- /dev/null
+++ b/cmds/ocm/commands/ocmcmds/components/transfer/utils.go
@@ -0,0 +1,52 @@
+package transfer
+
+import (
+ "fmt"
+
+ clictx "ocm.software/ocm/api/cli"
+ "ocm.software/ocm/api/ocm/tools/transfer/transferhandler"
+ "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/spiff"
+ "ocm.software/ocm/api/utils/out"
+ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/scriptoption"
+ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/transferhandleroption"
+ "ocm.software/ocm/cmds/ocm/common/options"
+)
+
+func ValidateHandler(o options.OptionSetProvider) error {
+ t := transferhandleroption.From(o)
+ if t.Path != "" {
+ s := scriptoption.From(o)
+ if len(s.ScriptData) > 0 {
+ if t.Path != "ocm/spiff" {
+ return fmt.Errorf("transfer handler %q not compatible with script option", t.Path)
+ }
+ if len(t.Config) > 0 {
+ return fmt.Errorf("transfer handler %q with config not compatible with script option", t.Path)
+ }
+ }
+ }
+ return nil
+}
+
+func DetermineTransferHandler(ctx clictx.Context, o options.OptionSetProvider) (transferhandler.TransferHandler, error) {
+ var (
+ thdlr transferhandler.TransferHandler
+ err error
+ )
+
+ opts := options.FindOptions[transferhandler.TransferOption](o)
+ th := transferhandleroption.From(o)
+ if th.Path != "" {
+ out.Outf(ctx, "using transfer handler %s\n", th.Path)
+ thdlr, err = transferhandler.For(ctx).ByName(ctx, th.Path, opts...)
+ } else {
+ transferopts := &spiff.Options{}
+ transferhandler.From(ctx.ConfigContext(), transferopts)
+ transferhandler.ApplyOptions(transferopts, opts...)
+ thdlr, err = spiff.New(transferopts)
+ }
+ if err != nil {
+ return nil, err
+ }
+ return thdlr, nil
+}
diff --git a/cmds/ocm/commands/ocmcmds/ctf/transfer/cmd.go b/cmds/ocm/commands/ocmcmds/ctf/transfer/cmd.go
index 227c458b11..ede839ef6f 100644
--- a/cmds/ocm/commands/ocmcmds/ctf/transfer/cmd.go
+++ b/cmds/ocm/commands/ocmcmds/ctf/transfer/cmd.go
@@ -9,7 +9,6 @@ import (
"ocm.software/ocm/api/ocm/extensions/repositories/ctf"
"ocm.software/ocm/api/ocm/tools/transfer"
"ocm.software/ocm/api/ocm/tools/transfer/transferhandler"
- "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/spiff"
"ocm.software/ocm/api/utils/accessobj"
common "ocm.software/ocm/api/utils/misc"
"ocm.software/ocm/cmds/ocm/commands/common/options/closureoption"
@@ -23,10 +22,11 @@ import (
"ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/skipupdateoption"
"ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/srcbyvalueoption"
"ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/stoponexistingoption"
+ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/transferhandleroption"
"ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/uploaderoption"
+ comptransfer "ocm.software/ocm/cmds/ocm/commands/ocmcmds/components/transfer"
"ocm.software/ocm/cmds/ocm/commands/ocmcmds/names"
"ocm.software/ocm/cmds/ocm/commands/verbs"
- "ocm.software/ocm/cmds/ocm/common/options"
"ocm.software/ocm/cmds/ocm/common/utils"
)
@@ -56,6 +56,7 @@ func NewCommand(ctx clictx.Context, names ...string) *cobra.Command {
stoponexistingoption.New(),
uploaderoption.New(ctx.OCMContext()),
scriptoption.New(),
+ transferhandleroption.New(),
)}, utils.Names(Names, names...)...)
}
@@ -77,7 +78,7 @@ $ ocm transfer ctf ctf.tgz ghcr.io/mandelsoft/components
func (o *Command) Complete(args []string) error {
o.SourceName = args[0]
o.TargetName = args[1]
- return nil
+ return comptransfer.ValidateHandler(o)
}
func (o *Command) Run() error {
@@ -103,14 +104,11 @@ func (o *Command) Run() error {
return err
}
- thdlr, err := spiff.New(
- append(options.FindOptions[transferhandler.TransferOption](o),
- spiff.Script(scriptoption.From(o).ScriptData),
- spiff.ScriptFilesystem(o.FileSystem()),
- )...)
+ thdlr, err := comptransfer.DetermineTransferHandler(o.Context, o)
if err != nil {
return err
}
+
a := &action{
printer: common.NewPrinter(o.Context.StdOut()),
target: target,
diff --git a/cmds/transferplugin/app/app.go b/cmds/transferplugin/app/app.go
new file mode 100644
index 0000000000..11d13f916c
--- /dev/null
+++ b/cmds/transferplugin/app/app.go
@@ -0,0 +1,31 @@
+package app
+
+import (
+ "ocm.software/ocm/api/ocm/plugin/ppi"
+ "ocm.software/ocm/api/ocm/plugin/ppi/cmds"
+ "ocm.software/ocm/api/version"
+ "ocm.software/ocm/cmds/transferplugin/config"
+ "ocm.software/ocm/cmds/transferplugin/transferhandlers"
+)
+
+func New() (ppi.Plugin, error) {
+ p := ppi.NewPlugin("transferplugin", version.Get().String())
+
+ p.SetShort("demo transfer handler plugin")
+ p.SetLong("plugin providing a transfer handler to enable value transport for dedicated external repositories.")
+ p.SetConfigParser(config.GetConfig)
+
+ err := p.RegisterTransferHandler(transferhandlers.New())
+ if err != nil {
+ return nil, err
+ }
+ return p, nil
+}
+
+func Run(args []string, opts ...cmds.Option) error {
+ p, err := New()
+ if err != nil {
+ return err
+ }
+ return cmds.NewPluginCommand(p, opts...).Execute(args)
+}
diff --git a/cmds/transferplugin/common/const.go b/cmds/transferplugin/common/const.go
new file mode 100644
index 0000000000..2a136fb8dd
--- /dev/null
+++ b/cmds/transferplugin/common/const.go
@@ -0,0 +1,3 @@
+package common
+
+const CONSUMER_TYPE = "demo"
diff --git a/cmds/transferplugin/config/config.go b/cmds/transferplugin/config/config.go
new file mode 100644
index 0000000000..9fcd1691b7
--- /dev/null
+++ b/cmds/transferplugin/config/config.go
@@ -0,0 +1,23 @@
+package config
+
+import (
+ "encoding/json"
+)
+
+type Config struct {
+ TransferRepositories TransferRepositories `json:"transferRepositories"`
+}
+
+type TransferRepositories struct {
+ Types map[string][]string `json:"types,omitempty"`
+}
+
+func GetConfig(raw json.RawMessage) (interface{}, error) {
+ var cfg Config
+
+ err := json.Unmarshal(raw, &cfg)
+ if err != nil {
+ return nil, err
+ }
+ return &cfg, nil
+}
diff --git a/cmds/transferplugin/main.go b/cmds/transferplugin/main.go
new file mode 100644
index 0000000000..866e7049e5
--- /dev/null
+++ b/cmds/transferplugin/main.go
@@ -0,0 +1,14 @@
+package main
+
+import (
+ "os"
+
+ "ocm.software/ocm/cmds/transferplugin/app"
+)
+
+func main() {
+ err := app.Run(os.Args[1:])
+ if err != nil {
+ os.Exit(1)
+ }
+}
diff --git a/cmds/transferplugin/transferhandlers/demo.go b/cmds/transferplugin/transferhandlers/demo.go
new file mode 100644
index 0000000000..c1a4880243
--- /dev/null
+++ b/cmds/transferplugin/transferhandlers/demo.go
@@ -0,0 +1,54 @@
+package transferhandlers
+
+import (
+ "slices"
+
+ "github.com/mandelsoft/goutils/optionutils"
+ "ocm.software/ocm/api/ocm/plugin/ppi"
+ "ocm.software/ocm/cmds/transferplugin/config"
+)
+
+const (
+ NAME = "demo"
+)
+
+func New() ppi.TransferHandler {
+ h := ppi.NewTransferHandler(NAME, "enable value transport for dedicated external repositories")
+
+ h.RegisterDecision(ppi.NewTransferResourceDecision(`value transport only for dedicated access types and service hosts`,
+ ForOptions(func(options *ppi.TransferOptions) bool { return optionutils.AsBool(options.ResourcesByValue) })))
+
+ h.RegisterDecision(ppi.NewTransferSourceDecision(`value transport only for dedicated access types and service hosts`,
+ ForOptions(func(options *ppi.TransferOptions) bool { return optionutils.AsBool(options.SourcesByValue) })))
+ return h
+}
+
+type OptionFunc func(opts *ppi.TransferOptions) bool
+
+func ForOptions(f OptionFunc) ppi.ArtifactQuestionFunc {
+ return func(p ppi.Plugin, question *ppi.ArtifactQuestionArguments) (bool, error) {
+ var cfg *config.Config
+
+ if question.Options.Special == nil {
+ c, err := p.GetConfig()
+ if c == nil || err != nil {
+ return false, err
+ }
+ cfg = c.(*config.Config)
+ } else {
+ c, err := config.GetConfig(*question.Options.Special)
+ if err != nil {
+ return false, err
+ }
+ if c != nil {
+ cfg = c.(*config.Config)
+ }
+ }
+
+ if list := cfg.TransferRepositories.Types[question.Artifact.AccessInfo.Kind]; list != nil {
+ host := question.Artifact.AccessInfo.Host
+ return slices.Contains(list, host), nil
+ }
+ return f(&question.Options), nil
+ }
+}
diff --git a/cmds/transferplugin/transferhandlers/demo_test.go b/cmds/transferplugin/transferhandlers/demo_test.go
new file mode 100644
index 0000000000..cf8d0282d4
--- /dev/null
+++ b/cmds/transferplugin/transferhandlers/demo_test.go
@@ -0,0 +1,91 @@
+package transferhandlers_test
+
+import (
+ "bytes"
+ "encoding/json"
+
+ . "github.com/mandelsoft/goutils/testutils"
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+
+ "github.com/mandelsoft/goutils/sliceutils"
+ "github.com/mandelsoft/vfs/pkg/osfs"
+ "github.com/mandelsoft/vfs/pkg/vfs"
+
+ "ocm.software/ocm/api/ocm"
+ metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1"
+ v2 "ocm.software/ocm/api/ocm/compdesc/versions/v2"
+ "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact"
+ "ocm.software/ocm/api/ocm/plugin/common"
+ "ocm.software/ocm/api/ocm/plugin/ppi"
+ "ocm.software/ocm/api/ocm/plugin/ppi/cmds"
+ "ocm.software/ocm/api/utils"
+ "ocm.software/ocm/api/utils/misc"
+ "ocm.software/ocm/cmds/transferplugin/app"
+)
+
+var _ = Describe("Test Environment", func() {
+ It("runs a demo question", func() {
+ var stdout bytes.Buffer
+ var stderr bytes.Buffer
+
+ config := Must(vfs.ReadFile(osfs.OsFs, "testdata/config"))
+
+ question := &ppi.ArtifactQuestionArguments{
+ Source: ppi.SourceComponentVersion{
+ Name: "",
+ Version: "",
+ Provider: metav1.Provider{},
+ Repository: ocm.GenericRepositorySpec{},
+ Labels: nil,
+ },
+ Artifact: ppi.Artifact{
+ Meta: v2.ElementMeta{},
+ Access: ocm.GenericAccessSpec{},
+ AccessInfo: ppi.AccessInfo{
+ Kind: ociartifact.Type,
+ Host: "ghcr.io",
+ Port: "",
+ Path: "",
+ Info: "",
+ },
+ },
+ Options: ppi.TransferOptions{},
+ }
+
+ in := Must(json.Marshal(question))
+
+ app.Run(sliceutils.AsSlice("--config", string(config), "transferhandler", "demo", ppi.Q_TRANSFER_RESOURCE), cmds.StdIn(bytes.NewBuffer(in)), cmds.StdOut(&stdout), cmds.StdErr(&stderr))
+ Expect(stdout.Bytes()).To(YAMLEqual(`
+decision: true
+`))
+ })
+
+ It("handles empty list", func() {
+ b := ppi.NewDecisionHandlerBase("x", "")
+ Expect(b.GetLabels()).NotTo(BeNil())
+ })
+
+ It("describes plugin", func() {
+ d := Must(app.New()).Descriptor()
+ p, out := misc.NewBufferedPrinter()
+ common.DescribePluginDescriptorCapabilities(nil, &d, p)
+ Expect(out.String()).To(StringEqualTrimmedWithContext(utils.Crop(`
+ Capabilities: Transfer Handlers
+ Description:
+ plugin providing a transfer handler to enable value transport for dedicated external repositories.
+
+ Transfer Handlers:
+ - Name: demo
+ enable value transport for dedicated external repositories
+ Questions:
+ - Name: transferresource (value transport for resources)
+ value transport only for dedicated access types and service hosts
+ consumes no labels
+ - Name: transfersource (value transport for sources)
+ value transport only for dedicated access types and service hosts
+ consumes no labels
+
+`, 2)))
+ })
+})
diff --git a/cmds/transferplugin/transferhandlers/suite_test.go b/cmds/transferplugin/transferhandlers/suite_test.go
new file mode 100644
index 0000000000..523a8af7f4
--- /dev/null
+++ b/cmds/transferplugin/transferhandlers/suite_test.go
@@ -0,0 +1,13 @@
+package transferhandlers_test
+
+import (
+ "testing"
+
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+)
+
+func TestConfig(t *testing.T) {
+ RegisterFailHandler(Fail)
+ RunSpecs(t, "transfer handler plugin Test Suite")
+}
diff --git a/cmds/transferplugin/transferhandlers/testdata/config b/cmds/transferplugin/transferhandlers/testdata/config
new file mode 100644
index 0000000000..338b2bcaab
--- /dev/null
+++ b/cmds/transferplugin/transferhandlers/testdata/config
@@ -0,0 +1,5 @@
+
+transferRepositories:
+ types:
+ ociArtifact:
+ - ghcr.io
\ No newline at end of file
diff --git a/docs/pluginreference/plugin.md b/docs/pluginreference/plugin.md
index dcbad48539..67aa4eac42 100644
--- a/docs/pluginreference/plugin.md
+++ b/docs/pluginreference/plugin.md
@@ -62,6 +62,7 @@ capabilities of the plugin.
* [plugin describe](plugin_describe.md) — describe plugin
* [plugin download](plugin_download.md) — download blob into filesystem
* [plugin info](plugin_info.md) — show plugin descriptor
+* [plugin transferhandler](plugin_transferhandler.md) — decide on a question related to a component version transport
* [plugin upload](plugin_upload.md) — upload specific operations
* [plugin valuemergehandler](plugin_valuemergehandler.md) — value merge handler operations
* [plugin valueset](plugin_valueset.md) — valueset operations
diff --git a/docs/pluginreference/plugin_transferhandler.md b/docs/pluginreference/plugin_transferhandler.md
new file mode 100644
index 0000000000..77463e85d3
--- /dev/null
+++ b/docs/pluginreference/plugin_transferhandler.md
@@ -0,0 +1,52 @@
+## plugin transferhandler — Decide On A Question Related To A Component Version Transport
+
+### Synopsis
+
+```bash
+plugin transferhandler []
+```
+
+### Options
+
+```text
+ -h, --help help for transferhandler
+```
+
+### Description
+
+The task of this command is to decide on questions related to the transport
+of component versions, their resources and sources.
+
+This command is only used for actions for which the transfer handler descriptor
+enables the particular question.
+
+The question arguments are passed as JSON structure on *stdin*,
+the result has to be returned as JSON document
+on *stdout*.
+
+There are several questions a handler can answer:
+- transferversion: This action answers the question, whether
+ a component version shall be transported at all and how it should be
+ transported. The argument is a ComponentVersionQuestion
+ and the result decision is extended by an optional transport context
+ consisting, of a new transport handler description and transport options.
+- enforcetransport: This action answers the question, whether
+ a component version shall be transported as if it is not yet present
+ in the target repository. The argument is a ComponentVersionQuestion.
+- updateversion: Update non-signature relevant information.
+ The argument is a ComponentVersionQuestion.
+- overwriteversion: Override signature-relevant information.
+ The argument is a ComponentVersionQuestion.
+- transferresource: Transport resource as value. The argument
+ is an ArtifactQuestion.
+- transfersource: Transport source as value. The argument
+ is an ArtifactQuestion.
+
+For detailed types see ocm.software/ocm/api/ocm/plugin/ppi/questions.go.
+
+### SEE ALSO
+
+#### Parents
+
+* [plugin](plugin.md) — OCM Plugin
+
diff --git a/docs/pluginreference/plugin_valueset_compose.md b/docs/pluginreference/plugin_valueset_compose.md
index 9a4a0f0894..f325f17423 100644
--- a/docs/pluginreference/plugin_valueset_compose.md
+++ b/docs/pluginreference/plugin_valueset_compose.md
@@ -21,7 +21,10 @@ The finally composed set has to be returned as JSON document
on *stdout*.
This command is only used, if for a value set descriptor configuration
-na direct composition rules are configured ([plugin descriptor](plugin_descriptor.md)).
+no direct composition rules are configured ([plugin descriptor](plugin_descriptor.md)).
+
+The purpose describes the purpose the values set is used for:
+- routingslip a value used for a routing slip entry.
If possible, predefined standard options should be used. In such a case only the
name field should be defined for an option. If required, new options can be
diff --git a/docs/reference/ocm_transfer_commontransportarchive.md b/docs/reference/ocm_transfer_commontransportarchive.md
index 799b4d1d35..196b37b61e 100644
--- a/docs/reference/ocm_transfer_commontransportarchive.md
+++ b/docs/reference/ocm_transfer_commontransportarchive.md
@@ -28,6 +28,7 @@ commontransportarchive, ctf
--script string config name of transfer handler script
-s, --scriptFile string filename of transfer handler script
-E, --stop-on-existing stop on existing component version in target repository
+ -T, --transfer-handler string transfer handler ([=)
-t, --type string archive format (directory, tar, tgz) (default "directory")
--uploader = repository uploader ([:[:[:]]]=) (default [])
```
@@ -181,6 +182,25 @@ Only one of the fields path or script can be used.
If no script option is given and the cli config defines a script default
this one is used.
+
+It is possible to use dedicated transfer handlers, either built-in ones or
+plugin based handlers. The option --transferHandler can be used to specify
+this handler using the hierarchical handler notation scheme.
+The following transfer handler names are supported:
+ - ocm/spiff: spiff transfer handler
+
+ The spiff transfer handler works on the standard transfer options
+ extended by dynamic programming based on spiff++.
+
+ - ocm/standard: standard transfer handler
+
+ The standard transfer handler works on the standard transfer options.
+
+ - plugin: [transfer handlers provided by plugins]
+
+ sub namespace of the form <plugin name>/<handler>
+
+
### Examples
```bash
diff --git a/docs/reference/ocm_transfer_componentversions.md b/docs/reference/ocm_transfer_componentversions.md
index 1c235f0e82..5dd82eddec 100644
--- a/docs/reference/ocm_transfer_componentversions.md
+++ b/docs/reference/ocm_transfer_componentversions.md
@@ -33,6 +33,7 @@ componentversions, componentversion, cv, components, component, comps, comp, c
--script string config name of transfer handler script
-s, --scriptFile string filename of transfer handler script
-E, --stop-on-existing stop on existing component version in target repository
+ -T, --transfer-handler string transfer handler ([=)
-t, --type string archive format (directory, tar, tgz) (default "directory")
--uploader = repository uploader ([:[:[:]]]=) (default [])
```
@@ -238,6 +239,25 @@ Only one of the fields path or script can be used.
If no script option is given and the cli config defines a script default
this one is used.
+
+It is possible to use dedicated transfer handlers, either built-in ones or
+plugin based handlers. The option --transferHandler can be used to specify
+this handler using the hierarchical handler notation scheme.
+The following transfer handler names are supported:
+ - ocm/spiff: spiff transfer handler
+
+ The spiff transfer handler works on the standard transfer options
+ extended by dynamic programming based on spiff++.
+
+ - ocm/standard: standard transfer handler
+
+ The standard transfer handler works on the standard transfer options.
+
+ - plugin: [transfer handlers provided by plugins]
+
+ sub namespace of the form <plugin name>/<handler>
+
+
### Examples
```bash