Skip to content

Commit

Permalink
component version check as lib function (#675)
Browse files Browse the repository at this point in the history
## Description

Move basic component check functionality from check command into
library.
Package `.../ocm/check` now provides basic chackes bases on some options
uses to specify
which checks should be performed.

## What type of PR is this? (check all applicable)

- [x] 🍕 Feature
- [ ] 🐛 Bug Fix
- [ ] 📝 Documentation Update
- [ ] 🎨 Style
- [ ] 🧑‍💻 Code Refactor
- [ ] 🔥 Performance Improvements
- [x] ✅ Test
- [ ] 🤖 Build
- [ ] 🔁 CI
- [ ] 📦 Chore (Release)
- [ ] ⏩ Revert

## Related Tickets & Documents

<!-- 
Please use this format link issue numbers: Fixes #123

https://docs.github.com/en/free-pro-team@latest/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword
-->
- Related Issue # (issue)
- Closes # (issue)
- Fixes # (issue)
> Remove if not applicable

## Screenshots

<!-- Visual changes require screenshots -->


## Added tests?

- [ ] 👍 yes
- [ ] 🙅 no, because they aren't needed
- [ ] 🙋 no, because I need help
- [ ] Separate ticket for tests # (issue/pr)

Please describe the tests that you ran to verify your changes. Provide
instructions so we can reproduce. Please also list any relevant details
for your test configuration


## Added to documentation?

- [ ] 📜 README.md
- [ ] 🙅 no documentation needed

## Checklist:

- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
  • Loading branch information
mandelsoft authored Feb 29, 2024
1 parent 2c12eb2 commit 09b2145
Show file tree
Hide file tree
Showing 9 changed files with 462 additions and 124 deletions.
144 changes: 24 additions & 120 deletions cmds/ocm/commands/ocmcmds/components/check/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
package check

import (
"encoding/json"
"fmt"

"github.com/open-component-model/ocm/pkg/contexts/ocm/utils/check"
"github.com/open-component-model/ocm/pkg/optionutils"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/util/json"

"github.com/open-component-model/ocm/cmds/ocm/commands/common/options/failonerroroption"
ocmcommon "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common"
Expand All @@ -22,9 +24,6 @@ import (
"github.com/open-component-model/ocm/pkg/common"
"github.com/open-component-model/ocm/pkg/contexts/clictx"
"github.com/open-component-model/ocm/pkg/contexts/ocm"
"github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc"
metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1"
"github.com/open-component-model/ocm/pkg/errors"
utils2 "github.com/open-component-model/ocm/pkg/utils"
)

Expand Down Expand Up @@ -171,71 +170,54 @@ func mapWideOutput(e interface{}) interface{} {

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

type CheckResult struct {
Missing Missing `json:"missing,omitempty"`
Resources []metav1.Identity `json:"resources,omitempty"`
Sources []metav1.Identity `json:"sources,omitempty"`
}

func newCheckResult() *CheckResult {
return &CheckResult{Missing: Missing{}}
}

func (r *CheckResult) IsEmpty() bool {
if r == nil {
return true
}
return len(r.Missing) == 0 && len(r.Resources) == 0 && len(r.Sources) == 0
}

type Missing map[common.NameVersion]common.History

func (n Missing) MarshalJSON() ([]byte, error) {
m := map[string]common.History{}
for k, v := range n {
m[k.String()] = v
}
return json.Marshal(m)
}
type CheckResult = check.Result

type Entry struct {
Status string `json:"status"`
ComponentVersion common.NameVersion `json:"componentVersion"`
Results *CheckResult `json:"missing,omitempty"`
Results *CheckResult `json:",inline"` // does not work
Error error `json:"error,omitempty"`
}

func (n CheckResult) MarshalJSON() ([]byte, error) {
m := map[string]common.History{}
for k, v := range n.Missing {
m[k.String()] = v
func (n Entry) MarshalJSON() ([]byte, error) {
m := map[string]interface{}{}
if n.Results != nil {
data, err := json.Marshal(n.Results)
if err != nil {
return nil, err
}
err = json.Unmarshal(data, &m)
if err != nil {
return nil, err
}
}
m["status"] = n.Status
m["componentVersion"] = n.ComponentVersion
if n.Error != nil {
m["error"] = n.Error
}
return json.Marshal(m)
}

type action struct {
erropt *failonerroroption.Option
options *Option
options *check.Options
}

func NewAction(opts *output.Options) processing.ProcessChain {
return comphdlr.Sort.Map((&action{
erropt: failonerroroption.From(opts),
options: From(opts),
options: optionutils.EvalOptions(check.Option(From(opts))),
}).Map)
}

type Cache = map[common.NameVersion]*CheckResult

func (a *action) Map(in interface{}) interface{} {
cache := Cache{}

i := in.(*comphdlr.Object)
o := &Entry{
ComponentVersion: common.VersionedElementKey(i.ComponentVersion),
}
status := ""
o.Results, o.Error = a.handle(cache, i.ComponentVersion, common.History{common.VersionedElementKey(i.ComponentVersion)})
o.Results, o.Error = a.options.For(i.ComponentVersion)
if o.Error != nil {
status = ",Error"
a.erropt.AddError(o.Error)
Expand Down Expand Up @@ -263,81 +245,3 @@ func (a *action) Map(in interface{}) interface{} {
}
return o
}

func (a *action) check(cache Cache, repo ocm.Repository, id common.NameVersion, h common.History) (*CheckResult, error) {
if r, ok := cache[id]; ok {
return r, nil
}

err := h.Add(ocm.KIND_COMPONENTVERSION, id)
if err != nil {
return nil, err
}
cv, err := repo.LookupComponentVersion(id.GetName(), id.GetVersion())
if err != nil {
if !errors.IsErrNotFound(err) {
return nil, err
}
err = nil
}
var r *CheckResult
if cv == nil {
r = &CheckResult{Missing: Missing{id: h}}
} else {
r, err = a.handle(cache, cv, h)
}
cache[id] = r
return r, err
}

func (a *action) handle(cache Cache, cv ocm.ComponentVersionAccess, h common.History) (*CheckResult, error) {
result := newCheckResult()

for _, r := range cv.GetDescriptor().References {
id := common.NewNameVersion(r.ComponentName, r.Version)
n, err := a.check(cache, cv.Repository(), id, h)
if err != nil {
return result, err
}
if n != nil && len(n.Missing) > 0 {
for k, v := range n.Missing {
result.Missing[k] = v
}
}
}

var err error

list := errors.ErrorList{}
if a.options.CheckLocalResources {
result.Resources, err = a.checkArtifacts(cv.GetContext(), cv.GetDescriptor().Resources)
list.Add(err)
}
if a.options.CheckLocalSources {
result.Sources, err = a.checkArtifacts(cv.GetContext(), cv.GetDescriptor().Sources)
list.Add(err)
}
if result.IsEmpty() {
result = nil
}
return result, list.Result()
}

func (a *action) checkArtifacts(ctx ocm.Context, accessor compdesc.ElementAccessor) ([]metav1.Identity, error) {
var result []metav1.Identity

list := errors.ErrorList{}
for i := 0; i < accessor.Len(); i++ {
e := accessor.Get(i).(compdesc.ElementArtifactAccessor)

m, err := ctx.AccessSpecForSpec(e.GetAccess())
if err != nil {
list.Add(err)
} else {
if !m.IsLocal(ctx) {
result = append(result, e.GetMeta().GetIdentity(accessor))
}
}
}
return result, list.Result()
}
4 changes: 2 additions & 2 deletions cmds/ocm/commands/ocmcmds/components/check/cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,14 @@ test.de/x v1 Incomplete test.de/z:v1[test.de/x:v1]
{
"items": [
{
"status": "Incomplete",
"componentVersion": "test.de/x:v1",
"missing": {
"test.de/z:v1": [
"test.de/x:v1",
"test.de/z:v1"
]
}
},
"status": "Incomplete"
}
]
}
Expand Down
7 changes: 7 additions & 0 deletions cmds/ocm/commands/ocmcmds/components/check/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
package check

import (
"github.com/open-component-model/ocm/pkg/contexts/ocm/utils/check"
"github.com/open-component-model/ocm/pkg/optionutils"
"github.com/spf13/pflag"

"github.com/open-component-model/ocm/cmds/ocm/pkg/options"
Expand All @@ -27,6 +29,11 @@ func NewOption() *Option {
return &Option{}
}

func (o *Option) ApplyTo(opts *check.Options) {
optionutils.ApplyOption(&o.CheckLocalSources, &opts.CheckLocalSources)
optionutils.ApplyOption(&o.CheckLocalResources, &opts.CheckLocalResources)
}

func (o *Option) AddFlags(fs *pflag.FlagSet) {
fs.BoolVarP(&o.CheckLocalResources, "local-resources", "R", false, "check also for describing resources with local access method, only")
fs.BoolVarP(&o.CheckLocalSources, "local-sources", "S", false, "check also for describing sources with local access method, only")
Expand Down
4 changes: 2 additions & 2 deletions pkg/contexts/ocm/accessmethods/ociartifact/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ The type specific specification fields are:
- **`imageReference`** *string*

OCI image/artifact reference following the possible docker schemes:
- `<repo>/<artifact>:<digest>@<tag>`
- `<host>[<port>]/<repo path>/<artifact>:<version>@<tag>`
- `<repo>/<artifact>:<tag>@<digest>`
- `<host>[<port>]/<repo path>/<artifact>:<tag>@<digest>`

### Go Bindings

Expand Down
151 changes: 151 additions & 0 deletions pkg/contexts/ocm/utils/check/check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Open Component Model contributors.
//
// SPDX-License-Identifier: Apache-2.0

package check

import (
"encoding/json"

"github.com/open-component-model/ocm/pkg/common"
"github.com/open-component-model/ocm/pkg/contexts/ocm"
"github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc"
metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1"
"github.com/open-component-model/ocm/pkg/errors"
"github.com/open-component-model/ocm/pkg/optionutils"
)

type Result struct {
Missing Missing `json:"missing,omitempty"`
Resources []metav1.Identity `json:"resources,omitempty"`
Sources []metav1.Identity `json:"sources,omitempty"`
}

func newResult() *Result {
return &Result{Missing: Missing{}}
}

func (r *Result) IsEmpty() bool {
if r == nil {
return true
}
return len(r.Missing) == 0 && len(r.Resources) == 0 && len(r.Sources) == 0
}

type Missing map[common.NameVersion]common.History

func (n Missing) MarshalJSON() ([]byte, error) {
m := map[string]common.History{}
for k, v := range n {
m[k.String()] = v
}
return json.Marshal(m)
}

type Cache = map[common.NameVersion]*Result

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

// Check provides a check object for checking component versions
// to completely available in an ocm repository.
// By default, it only checks the component reference closure
// to be in the same repository.
// Optionally, it is possible to check for inlined
// resources and sources, also.
func Check(opts ...Option) *Options {
return optionutils.EvalOptions(opts...)
}

func (a *Options) For(cv ocm.ComponentVersionAccess) (*Result, error) {
cache := Cache{}
return a.handle(cache, cv, common.History{common.VersionedElementKey(cv)})
}

func (a *Options) ForId(repo ocm.Repository, id common.NameVersion) (*Result, error) {
cv, err := repo.LookupComponentVersion(id.GetName(), id.GetVersion())
if err != nil {
return nil, err
}
defer cv.Close()
return a.For(cv)
}

func (a *Options) check(cache Cache, repo ocm.Repository, id common.NameVersion, h common.History) (*Result, error) {
if r, ok := cache[id]; ok {
return r, nil
}

err := h.Add(ocm.KIND_COMPONENTVERSION, id)
if err != nil {
return nil, err
}
cv, err := repo.LookupComponentVersion(id.GetName(), id.GetVersion())
if err != nil {
if !errors.IsErrNotFound(err) {
return nil, err
}
err = nil
}

var r *Result
if cv == nil {
r = &Result{Missing: Missing{id: h}}
} else {
defer cv.Close()
r, err = a.handle(cache, cv, h)
}
cache[id] = r
return r, err
}

func (a *Options) handle(cache Cache, cv ocm.ComponentVersionAccess, h common.History) (*Result, error) {
result := newResult()

for _, r := range cv.GetDescriptor().References {
id := common.NewNameVersion(r.ComponentName, r.Version)
n, err := a.check(cache, cv.Repository(), id, h)
if err != nil {
return result, err
}
if n != nil && len(n.Missing) > 0 {
for k, v := range n.Missing {
result.Missing[k] = v
}
}
}

var err error

list := errors.ErrorList{}
if optionutils.AsBool(a.CheckLocalResources) {
result.Resources, err = a.checkArtifacts(cv.GetContext(), cv.GetDescriptor().Resources)
list.Add(err)
}
if optionutils.AsBool(a.CheckLocalSources) {
result.Sources, err = a.checkArtifacts(cv.GetContext(), cv.GetDescriptor().Sources)
list.Add(err)
}
if result.IsEmpty() {
result = nil
}
return result, list.Result()
}

func (a *Options) checkArtifacts(ctx ocm.Context, accessor compdesc.ElementAccessor) ([]metav1.Identity, error) {
var result []metav1.Identity

list := errors.ErrorList{}
for i := 0; i < accessor.Len(); i++ {
e := accessor.Get(i).(compdesc.ElementArtifactAccessor)

m, err := ctx.AccessSpecForSpec(e.GetAccess())
if err == nil {
if !m.IsLocal(ctx) {
result = append(result, e.GetMeta().GetIdentity(accessor))
}
} else {
list.Add(err)
}
}
return result, list.Result()
}
Loading

0 comments on commit 09b2145

Please sign in to comment.