}
+```
+
+### Options
+
+```
+ --actual use actual component descriptor
+ -c, --constraints constraints version constraint
+ -H, --hash string hash algorithm (default "sha256")
+ -h, --help help for componentversions
+ --latest restrict component versions to latest
+ --lookup stringArray repository name or spec for closure lookup fallback
+ -N, --normalization string normalization algorithm (default "jsonNormalisation/v1")
+ -o, --output string output mode (JSON, json, wide, yaml)
+ -r, --recursive follow component reference nesting
+ --repo string repository name or spec
+ -s, --sort stringArray sort fields
+```
+
+### Description
+
+
+Hash lists normalized forms for all component versions specified, if only a component is specified
+all versions are listed.
+
+If the option --constraints
is given, and no version is specified for a component, only versions matching
+the given version constraints (semver https://github.com/Masterminds/semver) are selected. With --latest
only
+the latest matching versions will be selected.
+
+If the option --actual
is given the component descriptor actually
+found is used as it is, otherwise the required digests are calculated on-the-fly.
+
+With the option --recursive
the complete reference tree of a component reference is traversed.
+
+If a component lookup for building a reference closure is required
+the --lookup
option can be used to specify a fallback
+lookup repository.
+By default the component versions are searched in the repository
+holding the component version for which the closure is determined.
+For *Component Archives* this is never possible, because it only
+contains a single component version. Therefore, in this scenario
+this option must always be specified to be able to follow component
+references.
+
+The following normalization modes are supported with option --normalization
:
+
+ - jsonNormalisation/v1
(default):
+ - jsonNormalisation/v2
:
+
+
+The following hash modes are supported with option --hash
:
+
+ - NO-DIGEST
:
+ - sha256
(default):
+ - sha512
:
+
+If the --repo
option is specified, the given names are interpreted
+relative to the specified repository using the syntax
+
+
+ <component>[:<version>]
+
+
+If no --repo
option is specified the given names are interpreted
+as located OCM component version references:
+
+
+ [<repo type>::]<host>[:<port>][/<base path>]//<component>[:<version>]
+
+
+Additionally there is a variant to denote common transport archives
+and general repository specifications
+
+
+ [<repo type>::]<filepath>|<spec json>[//<component>[:<version>]]
+
+
+The --repo
option takes an OCM repository specification:
+
+
+ [<repo type>::]<configured name>|<file path>|<spec json>
+
+
+For the *Common Transport Format* the types directory
,
+tar
or tgz
is possible.
+
+Using the JSON variant any repository type supported by the
+linked library can be used:
+
+Dedicated OCM repository types:
+- `ComponentArchive`
+
+OCI Repository types (using standard component repository to OCI mapping):
+- `ArtifactSet`
+- `CommonTransportFormat`
+- `DockerDaemon`
+- `Empty`
+- `OCIRegistry`
+- `oci`
+- `ociRegistry`
+
+With the option --output
the output mode can be selected.
+The following modes are supported:
+ - JSON
+ - json
+ - wide
+ - yaml
+
+
+### Examples
+
+```
+$ ocm hash componentversion ghcr.io/mandelsoft/kubelink
+$ ocm hash componentversion --repo OCIRegistry:ghcr.io mandelsoft/kubelink
+```
+
+### SEE ALSO
+
+##### Parents
+
+* [ocm hash](ocm_hash.md) — Hash and normalization operations
+* [ocm](ocm.md) — Open Component Model command line client
+
diff --git a/hack/format.sh b/hack/format.sh
index eebad5780..be324d634 100755
--- a/hack/format.sh
+++ b/hack/format.sh
@@ -16,7 +16,7 @@ pkgprefix="github.com/open-component-model/ocm"
log "Format with gci"
GCIFMT=( -s standard -s blank -s dot -s default -s="prefix(${pkgprefix})" --custom-order )
-gci diff --skip-generated "${GCIFMT[@]}" $@ \
+gci diff --skip-generated "${GCIFMT[@]}" $@ -h for additional help.
+{{end}}{{if .HasExample}}
Examples:
{{.Example | indent 2}}{{end}}{{if .HasHelpSubCommands}}
diff --git a/pkg/common/walk.go b/pkg/common/walk.go
index a7f93a3e1..8e42885f7 100644
--- a/pkg/common/walk.go
+++ b/pkg/common/walk.go
@@ -8,9 +8,9 @@ import (
"github.com/open-component-model/ocm/pkg/utils"
)
-type NameVersionInfo map[NameVersion]interface{}
+type NameVersionInfo[T any] map[NameVersion]T
-func (s NameVersionInfo) Add(nv NameVersion, data ...interface{}) bool {
+func (s NameVersionInfo[T]) Add(nv NameVersion, data ...T) bool {
if _, ok := s[nv]; !ok {
s[nv] = utils.Optional(data...)
return true
@@ -18,28 +18,32 @@ func (s NameVersionInfo) Add(nv NameVersion, data ...interface{}) bool {
return false
}
-func (s NameVersionInfo) Contains(nv NameVersion) bool {
+func (s NameVersionInfo[T]) Contains(nv NameVersion) bool {
_, ok := s[nv]
return ok
}
-type WalkingState struct {
- Closure NameVersionInfo
+type WalkingState[T any] struct {
+ Closure NameVersionInfo[T]
History History
}
-func NewWalkingState() WalkingState {
- return WalkingState{Closure: NameVersionInfo{}}
+func NewWalkingState[T any]() WalkingState[T] {
+ return WalkingState[T]{Closure: NameVersionInfo[T]{}}
}
-func (s *WalkingState) Add(kind string, nv NameVersion) (bool, error) {
+func (s *WalkingState[T]) Add(kind string, nv NameVersion) (bool, error) {
if err := s.History.Add(kind, nv); err != nil {
return false, err
}
return s.Closure.Add(nv), nil
}
-func (s *WalkingState) Contains(nv NameVersion) bool {
+func (s *WalkingState[T]) Contains(nv NameVersion) bool {
_, ok := s.Closure[nv]
return ok
}
+
+func (s *WalkingState[T]) Get(nv NameVersion) T {
+ return s.Closure[nv]
+}
diff --git a/pkg/contexts/ocm/compdesc/signing.go b/pkg/contexts/ocm/compdesc/signing.go
index 0487352ae..c4a8b810d 100644
--- a/pkg/contexts/ocm/compdesc/signing.go
+++ b/pkg/contexts/ocm/compdesc/signing.go
@@ -64,6 +64,23 @@ func Hash(cd *ComponentDescriptor, normAlgo string, hash hash.Hash) (string, err
return hex.EncodeToString(hash.Sum(nil)), nil
}
+func NormHash(cd *ComponentDescriptor, normAlgo string, hash hash.Hash) ([]byte, string, error) {
+ if hash == nil {
+ return nil, metav1.NoDigest, nil
+ }
+
+ normalized, err := Normalize(cd, normAlgo)
+ if err != nil {
+ return nil, "", fmt.Errorf("failed normalising component descriptor %w", err)
+ }
+ hash.Reset()
+ if _, err = hash.Write(normalized); err != nil {
+ return nil, "", fmt.Errorf("failed hashing the normalisedComponentDescriptorJson: %w", err)
+ }
+
+ return normalized, hex.EncodeToString(hash.Sum(nil)), nil
+}
+
// Sign signs the given component-descriptor with the signer.
// The component-descriptor has to contain digests for componentReferences and resources.
func Sign(cd *ComponentDescriptor, privateKey interface{}, signer signing.Signer, hasher signing.Hasher, signatureName, issuer string) error {
diff --git a/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/jsonscheme/bindata.go b/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/jsonscheme/bindata.go
index 254f2bbed..ec4279f05 100644
--- a/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/jsonscheme/bindata.go
+++ b/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/jsonscheme/bindata.go
@@ -94,7 +94,7 @@ func ResourcesComponentDescriptorOcmV3SchemaYaml() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "../../../../../../../../resources/component-descriptor-ocm-v3-schema.yaml", size: 10607, mode: os.FileMode(436), modTime: time.Unix(1671033040, 0)}
+ info := bindataFileInfo{name: "../../../../../../../../resources/component-descriptor-ocm-v3-schema.yaml", size: 10607, mode: os.FileMode(436), modTime: time.Unix(1671551562, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
diff --git a/pkg/contexts/ocm/compdesc/versions/v2/jsonscheme/bindata.go b/pkg/contexts/ocm/compdesc/versions/v2/jsonscheme/bindata.go
index 475c05057..cac6ef2dd 100644
--- a/pkg/contexts/ocm/compdesc/versions/v2/jsonscheme/bindata.go
+++ b/pkg/contexts/ocm/compdesc/versions/v2/jsonscheme/bindata.go
@@ -94,7 +94,7 @@ func ResourcesComponentDescriptorV2SchemaYaml() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "../../../../../../../resources/component-descriptor-v2-schema.yaml", size: 10392, mode: os.FileMode(436), modTime: time.Unix(1671033040, 0)}
+ info := bindataFileInfo{name: "../../../../../../../resources/component-descriptor-v2-schema.yaml", size: 10392, mode: os.FileMode(436), modTime: time.Unix(1671551562, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
diff --git a/pkg/contexts/ocm/signing/handle.go b/pkg/contexts/ocm/signing/handle.go
index 793959d32..ad3447c80 100644
--- a/pkg/contexts/ocm/signing/handle.go
+++ b/pkg/contexts/ocm/signing/handle.go
@@ -18,25 +18,44 @@ import (
"github.com/open-component-model/ocm/pkg/utils"
)
+type VersionInfo struct {
+ Descriptor *compdesc.ComponentDescriptor
+ Digest *metav1.DigestSpec
+ Signed bool
+}
+
func ToDigestSpec(v interface{}) *metav1.DigestSpec {
if v == nil {
return nil
}
- return v.(*metav1.DigestSpec)
+ return v.(*VersionInfo).Digest
+}
+
+type WalkingState = common.WalkingState[*VersionInfo]
+
+func NewWalkingState() WalkingState {
+ return common.NewWalkingState[*VersionInfo]()
}
-func Apply(printer common.Printer, state *common.WalkingState, cv ocm.ComponentVersionAccess, opts *Options, closecv ...bool) (*metav1.DigestSpec, error) {
+func Apply(printer common.Printer, state *WalkingState, cv ocm.ComponentVersionAccess, opts *Options, closecv ...bool) (*metav1.DigestSpec, error) {
if printer == nil {
printer = common.NewPrinter(nil)
}
if state == nil {
- s := common.NewWalkingState()
+ s := common.NewWalkingState[*VersionInfo]()
state = &s
}
return apply(printer, *state, cv, opts, utils.Optional(closecv...))
}
-func apply(printer common.Printer, state common.WalkingState, cv ocm.ComponentVersionAccess, opts *Options, closecv bool) (d *metav1.DigestSpec, efferr error) {
+func RequireReProcessing(vi *VersionInfo, opts *Options) bool {
+ if vi == nil {
+ return true
+ }
+ return opts.DoSign() && !vi.Signed
+}
+
+func apply(printer common.Printer, state WalkingState, cv ocm.ComponentVersionAccess, opts *Options, closecv bool) (d *metav1.DigestSpec, efferr error) {
var closer errors.ErrorFunction
if closecv {
closer = cv.Close
@@ -44,16 +63,28 @@ func apply(printer common.Printer, state common.WalkingState, cv ocm.ComponentVe
nv := common.VersionedElementKey(cv)
defer errors.PropagateErrorf(&efferr, closer, "%s", state.History.Append(nv))
+ vi := state.Get(nv)
if ok, err := state.Add(ocm.KIND_COMPONENTVERSION, nv); !ok {
- return ToDigestSpec(state.Closure[nv]), err
+ if err != nil || !RequireReProcessing(vi, opts) {
+ return ToDigestSpec(vi), err
+ }
}
- return _apply(printer, state, nv, cv, opts)
+ return _apply(printer, state, nv, cv, vi, opts)
}
-func _apply(printer common.Printer, state common.WalkingState, nv common.NameVersion, cv ocm.ComponentVersionAccess, opts *Options) (*metav1.DigestSpec, error) {
- cd := cv.GetDescriptor().Copy()
+func _apply(printer common.Printer, state WalkingState, nv common.NameVersion, cv ocm.ComponentVersionAccess, vi *VersionInfo, opts *Options) (*metav1.DigestSpec, error) {
+ var cd *compdesc.ComponentDescriptor
+
+ prefix := ""
+ if vi == nil {
+ cd = cv.GetDescriptor().Copy()
+ vi = &VersionInfo{Descriptor: cd}
+ } else {
+ cd = vi.Descriptor
+ prefix = "re"
+ }
octx := cv.GetContext()
- printer.Printf("applying to version %q...\n", nv)
+ printer.Printf("%sapplying to version %q...\n", prefix, nv)
signatureNames := opts.SignatureNames
if len(signatureNames) == 0 {
@@ -73,61 +104,64 @@ func _apply(printer common.Printer, state common.WalkingState, nv common.NameVer
}
}
- if err := calculateReferenceDigests(printer, cd, state, opts); err != nil {
- return nil, err
- }
-
- blobdigesters := cv.GetContext().BlobDigesters()
- for i, res := range cv.GetResources() {
- raw := &cd.Resources[i]
- acc, err := res.Access()
- if err != nil {
- return nil, errors.Wrapf(err, resMsg(raw, "", "failed getting access for resource"))
- }
- if _, ok := opts.SkipAccessTypes[acc.GetKind()]; ok {
- // set the do not sign digest notation on skip-access-type resources
- cd.Resources[i].Digest = metav1.NewExcludeFromSignatureDigest()
- continue
- }
- // special digest notation indicates to not digest the content
- if cd.Resources[i].Digest != nil && reflect.DeepEqual(cd.Resources[i].Digest, metav1.NewExcludeFromSignatureDigest()) {
- continue
+ if vi.Digest == nil {
+ if err := calculateReferenceDigests(printer, cd, state, opts); err != nil {
+ return nil, err
}
- meth, err := acc.AccessMethod(cv)
- if err != nil {
- return nil, errors.Wrapf(err, resMsg(raw, acc.Describe(octx), "failed creating access for resource"))
- }
- var req []cpi.DigesterType
- if raw.Digest != nil {
- req = []cpi.DigesterType{
- {
- HashAlgorithm: raw.Digest.HashAlgorithm,
- NormalizationAlgorithm: raw.Digest.NormalisationAlgorithm,
- },
+ blobdigesters := cv.GetContext().BlobDigesters()
+ for i, res := range cv.GetResources() {
+ raw := &cd.Resources[i]
+ acc, err := res.Access()
+ if err != nil {
+ return nil, errors.Wrapf(err, resMsg(raw, "", "failed getting access for resource"))
+ }
+ if _, ok := opts.SkipAccessTypes[acc.GetKind()]; ok {
+ // set the do not sign digest notation on skip-access-type resources
+ cd.Resources[i].Digest = metav1.NewExcludeFromSignatureDigest()
+ continue
+ }
+ // special digest notation indicates to not digest the content
+ if cd.Resources[i].Digest != nil && reflect.DeepEqual(cd.Resources[i].Digest, metav1.NewExcludeFromSignatureDigest()) {
+ continue
+ }
+
+ meth, err := acc.AccessMethod(cv)
+ if err != nil {
+ return nil, errors.Wrapf(err, resMsg(raw, acc.Describe(octx), "failed creating access for resource"))
}
+ var req []cpi.DigesterType
+ if raw.Digest != nil {
+ req = []cpi.DigesterType{
+ {
+ HashAlgorithm: raw.Digest.HashAlgorithm,
+ NormalizationAlgorithm: raw.Digest.NormalisationAlgorithm,
+ },
+ }
+ }
+ digest, err := blobdigesters.DetermineDigests(res.Meta().GetType(), opts.Hasher, opts.Registry, meth, req...)
+ if err != nil {
+ return nil, errors.Wrapf(err, resMsg(raw, acc.Describe(octx), "failed determining digest for resource"))
+ }
+ if len(digest) == 0 {
+ return nil, errors.Newf(resMsg(raw, acc.Describe(octx), "no digester accepts resource"))
+ }
+ if raw.Digest != nil && !reflect.DeepEqual(*raw.Digest, digest[0]) {
+ return nil, errors.Newf(resMsg(raw, acc.Describe(octx), "calculated resource digest (%+v) mismatches existing digest (%+v) for", digest, raw.Digest))
+ }
+ cd.Resources[i].Digest = &digest[0]
+ printer.Printf(" resource %d: %s: digest %s\n", i, res.Meta().GetIdentity(cv.GetDescriptor().Resources), &digest[0])
}
- digest, err := blobdigesters.DetermineDigests(res.Meta().GetType(), opts.Hasher, opts.Registry, meth, req...)
+ digest, err := compdesc.Hash(cd, opts.NormalizationAlgo, opts.Hasher.Create())
if err != nil {
- return nil, errors.Wrapf(err, resMsg(raw, acc.Describe(octx), "failed determining digest for resource"))
- }
- if len(digest) == 0 {
- return nil, errors.Newf(resMsg(raw, acc.Describe(octx), "no digester accepts resource"))
+ return nil, errors.Wrapf(err, "failed hashing component descriptor")
}
- if raw.Digest != nil && !reflect.DeepEqual(*raw.Digest, digest[0]) {
- return nil, errors.Newf(resMsg(raw, acc.Describe(octx), "calculated resource digest (%+v) mismatches existing digest (%+v) for", digest, raw.Digest))
+ spec := &metav1.DigestSpec{
+ HashAlgorithm: opts.Hasher.Algorithm(),
+ NormalisationAlgorithm: opts.NormalizationAlgo,
+ Value: digest,
}
- cd.Resources[i].Digest = &digest[0]
- printer.Printf(" resource %d: %s: digest %s\n", i, res.Meta().GetIdentity(cv.GetDescriptor().Resources), &digest[0])
- }
- digest, err := compdesc.Hash(cd, opts.NormalizationAlgo, opts.Hasher.Create())
- if err != nil {
- return nil, errors.Wrapf(err, "failed hashing component descriptor")
- }
- spec := &metav1.DigestSpec{
- HashAlgorithm: opts.Hasher.Algorithm(),
- NormalisationAlgorithm: opts.NormalizationAlgo,
- Value: digest,
+ vi.Digest = spec
}
if opts.DoVerify() {
@@ -138,7 +172,7 @@ func _apply(printer common.Printer, state common.WalkingState, nv common.NameVer
found := cd.GetSignatureIndex(opts.SignatureName())
if opts.DoSign() && (!opts.DoVerify() || found == -1) {
- sig, err := opts.Signer.Sign(digest, opts.Hasher.Crypto(), opts.Issuer, opts.PrivateKey())
+ sig, err := opts.Signer.Sign(vi.Digest.Value, opts.Hasher.Crypto(), opts.Issuer, opts.PrivateKey())
if err != nil {
return nil, errors.Wrapf(err, "failed signing component descriptor")
}
@@ -151,7 +185,7 @@ func _apply(printer common.Printer, state common.WalkingState, nv common.NameVer
}
signature := metav1.Signature{
Name: opts.SignatureName(),
- Digest: *spec,
+ Digest: *vi.Digest,
Signature: metav1.SignatureSpec{
Algorithm: sig.Algorithm,
Value: sig.Value,
@@ -175,10 +209,11 @@ func _apply(printer common.Printer, state common.WalkingState, nv common.NameVer
}
if opts.DoSign() {
orig.Signatures = cd.Signatures
+ vi.Signed = true
}
}
- state.Closure[nv] = spec
- return spec, nil
+ state.Closure[nv] = vi
+ return vi.Digest, nil
}
func refMsg(ref compdesc.ComponentReference, msg string, args ...interface{}) string {
@@ -192,7 +227,7 @@ func resMsg(ref *compdesc.Resource, acc string, msg string, args ...interface{})
return fmt.Sprintf("%s %s:%s", fmt.Sprintf(msg, args...), ref.Name, ref.Version)
}
-func doVerify(printer common.Printer, cd *compdesc.ComponentDescriptor, state common.WalkingState, signatureNames []string, opts *Options) error {
+func doVerify(printer common.Printer, cd *compdesc.ComponentDescriptor, state WalkingState, signatureNames []string, opts *Options) error {
var err error
found := []string{}
for _, n := range signatureNames {
@@ -236,7 +271,7 @@ func doVerify(printer common.Printer, cd *compdesc.ComponentDescriptor, state co
return nil
}
-func calculateReferenceDigests(printer common.Printer, cd *compdesc.ComponentDescriptor, state common.WalkingState, opts *Options) error {
+func calculateReferenceDigests(printer common.Printer, cd *compdesc.ComponentDescriptor, state WalkingState, opts *Options) error {
for i, reference := range cd.References {
var calculatedDigest *metav1.DigestSpec
if reference.Digest == nil && !opts.DoUpdate() {
diff --git a/pkg/contexts/ocm/transfer/transfer.go b/pkg/contexts/ocm/transfer/transfer.go
index 8e440d36b..1fe13d927 100644
--- a/pkg/contexts/ocm/transfer/transfer.go
+++ b/pkg/contexts/ocm/transfer/transfer.go
@@ -20,7 +20,9 @@ import (
"github.com/open-component-model/ocm/pkg/runtime"
)
-type TransportClosure = common.NameVersionInfo
+type WalkingState = common.WalkingState[*struct{}]
+
+type TransportClosure = common.NameVersionInfo[*struct{}]
func TransferVersion(printer common.Printer, closure TransportClosure, src ocmcpi.ComponentVersionAccess, tgt ocmcpi.Repository, handler transferhandler.TransferHandler) error {
if closure == nil {
@@ -29,11 +31,11 @@ func TransferVersion(printer common.Printer, closure TransportClosure, src ocmcp
if printer == nil {
printer = common.NewPrinter(nil)
}
- state := common.WalkingState{Closure: closure}
+ state := WalkingState{Closure: closure}
return transferVersion(printer, Logger(src), state, src, tgt, handler)
}
-func transferVersion(printer common.Printer, log logging.Logger, state common.WalkingState, src ocmcpi.ComponentVersionAccess, tgt ocmcpi.Repository, handler transferhandler.TransferHandler) error {
+func transferVersion(printer common.Printer, log logging.Logger, state WalkingState, src ocmcpi.ComponentVersionAccess, tgt ocmcpi.Repository, handler transferhandler.TransferHandler) error {
nv := common.VersionedElementKey(src)
log = log.WithValues("history", state.History.String(), "version", nv)
if ok, err := state.Add(ocm.KIND_COMPONENTVERSION, nv); !ok {
diff --git a/pkg/docker/fetcher.go b/pkg/docker/fetcher.go
index c1a2eb2f5..f2116163c 100644
--- a/pkg/docker/fetcher.go
+++ b/pkg/docker/fetcher.go
@@ -139,7 +139,11 @@ func (r dockerFetcher) Fetch(ctx context.Context, desc ocispec.Descriptor) (io.R
}
func (r dockerFetcher) open(ctx context.Context, req *request, mediatype string, offset int64) (_ io.ReadCloser, retErr error) {
- req.header.Set("Accept", strings.Join([]string{mediatype, `*/*`}, ", "))
+ mt := "*/*"
+ if mediatype != "" {
+ mt = mediatype + ", " + mt
+ }
+ req.header.Set("Accept", mt)
if offset > 0 {
// Note: "Accept-Ranges: bytes" cannot be trusted as some endpoints