diff --git a/bindings-go/Makefile b/bindings-go/Makefile index 01dc5a2f..582a7ec5 100644 --- a/bindings-go/Makefile +++ b/bindings-go/Makefile @@ -22,6 +22,10 @@ install-requirements: format: @$(REPO_ROOT)/hack/format.sh $(REPO_ROOT)/apis $(REPO_ROOT)/codec $(REPO_ROOT)/examples +.PHONY: generate +generate: + @$(REPO_ROOT)/hack/generate-code.sh + .PHONY: test test: @go test $(REPO_ROOT)/... @@ -31,6 +35,9 @@ check: @echo "Run lint"; golangci-lint run --timeout 10m $(REPO_ROOT)/... @$(REPO_ROOT)/hack/check.sh $(REPO_ROOT)/apis $(REPO_ROOT)/codec $(REPO_ROOT)/examples +.PHONY: verify +verify: check test + .PHONY: revendor revendor: @go mod tidy diff --git a/bindings-go/README.md b/bindings-go/README.md index b1103d26..2a4cac7c 100644 --- a/bindings-go/README.md +++ b/bindings-go/README.md @@ -34,7 +34,9 @@ component: provider: internal - repositoryContexts: [] + repositoryContexts: + - type: ociRegistry + baseUrl: example.com sources: [] componentReferences: [] @@ -77,6 +79,25 @@ component: } ``` +##### Repository Context + +:warning: Note that the following examples use the above described component descriptor. + +```go +component := &v2.ComponentDescriptor{} +err := codec.Decode(data, component) +check(err) + +// get the latest repository context. +// the context is returned as unstructured object (similar to the access types) as differnt repository types +// with different attributes are possible. +unstructuredRepoCtx := component.GetEffectiveRepositoryContext() +// decode the unstructured type into a specific type +ociRepo := &v2.OCIRegistryRepository{} +check(unstructuredRepoCtx.DecodeInto(ociRepo)) +fmt.Printf("%s\n", ociRepo.BaseURL) // prints "example.com" +``` + ##### Select Resources :warning: Note that the following examples use the above described component descriptor. @@ -99,9 +120,8 @@ fmt.Printf("%#v\n", res) // get the access for a resource // specific access type can be decoded using the access type codec. -accessTypeCodec := v2.NewCodec(nil, nil, nil) ociAccess := &v2.OCIRegistryAccess{} -check(accessTypeCodec.Decode(res.Access.Raw, ociAccess)) +check(res.Access.DecodeInto(ociAccess)) fmt.Println(ociAccess.ImageReference) // prints: k8s.gcr.io/hyperkube:v1.16.4 ``` diff --git a/bindings-go/apis/v2/accesstypes.go b/bindings-go/apis/v2/accesstypes.go index 358b477a..d283d444 100644 --- a/bindings-go/apis/v2/accesstypes.go +++ b/bindings-go/apis/v2/accesstypes.go @@ -14,19 +14,14 @@ package v2 -import ( - "encoding/json" - - "github.com/ghodss/yaml" -) - // KnownAccessTypes contains all known access serializer var KnownAccessTypes = KnownTypes{ - OCIRegistryType: DefaultJSONTypedObjectCodec, - OCIBlobType: DefaultJSONTypedObjectCodec, - GitHubAccessType: DefaultJSONTypedObjectCodec, - WebType: DefaultJSONTypedObjectCodec, - LocalFilesystemBlobType: DefaultJSONTypedObjectCodec, + OCIRegistryType: DefaultJSONTypedObjectCodec, + OCIBlobType: DefaultJSONTypedObjectCodec, + RelativeOciReferenceType: DefaultJSONTypedObjectCodec, + GitHubAccessType: DefaultJSONTypedObjectCodec, + WebType: DefaultJSONTypedObjectCodec, + LocalFilesystemBlobType: DefaultJSONTypedObjectCodec, } // OCIRegistryType is the access type of a oci registry. @@ -50,24 +45,10 @@ func NewOCIRegistryAccess(ref string) TypedObjectAccessor { } } -func (_ *OCIRegistryAccess) GetType() string { +func (a *OCIRegistryAccess) GetType() string { return OCIRegistryType } -func (O OCIRegistryAccess) GetData() ([]byte, error) { - return json.Marshal(O) -} - -func (O *OCIRegistryAccess) SetData(bytes []byte) error { - var newOCIImage OCIRegistryAccess - if err := json.Unmarshal(bytes, &newOCIImage); err != nil { - return err - } - - O.ImageReference = newOCIImage.ImageReference - return nil -} - // RelativeOciReferenceType is the access type of a relative oci reference. const RelativeOciReferenceType = "relativeOciReference" @@ -93,20 +74,6 @@ func (_ *RelativeOciAccess) GetType() string { return RelativeOciReferenceType } -func (O RelativeOciAccess) GetData() ([]byte, error) { - return json.Marshal(O) -} - -func (O *RelativeOciAccess) SetData(bytes []byte) error { - var newRelativeOCIImage RelativeOciAccess - if err := json.Unmarshal(bytes, &newRelativeOCIImage); err != nil { - return err - } - - O.Reference = newRelativeOCIImage.Reference - return nil -} - // OCIBlobType is the access type of a oci blob in a manifest. const OCIBlobType = "ociBlob" @@ -144,23 +111,6 @@ func (_ *OCIBlobAccess) GetType() string { return OCIBlobType } -func (a OCIBlobAccess) GetData() ([]byte, error) { - return json.Marshal(a) -} - -func (a *OCIBlobAccess) SetData(bytes []byte) error { - var newOCILayer OCIBlobAccess - if err := json.Unmarshal(bytes, &newOCILayer); err != nil { - return err - } - - a.Reference = newOCILayer.Reference - a.MediaType = newOCILayer.MediaType - a.Digest = newOCILayer.Digest - a.Size = newOCILayer.Size - return nil -} - // LocalOCIBlobType is the access type of a oci blob in the current component descriptor manifest. const LocalOCIBlobType = "localOciBlob" @@ -185,20 +135,7 @@ func (_ *LocalOCIBlobAccess) GetType() string { return LocalOCIBlobType } -func (a LocalOCIBlobAccess) GetData() ([]byte, error) { - return json.Marshal(a) -} - -func (a *LocalOCIBlobAccess) SetData(bytes []byte) error { - var newAccess OCIBlobAccess - if err := json.Unmarshal(bytes, &newAccess); err != nil { - return err - } - a.Digest = newAccess.Digest - return nil -} - -// LocalBlobType is the access type of a oci blob in a manifest. +// LocalFilesystemBlobType is the access type of a blob in a local filesystem. const LocalFilesystemBlobType = "localFilesystemBlob" // NewLocalFilesystemBlobAccess creates a new localFilesystemBlob accessor. @@ -226,19 +163,6 @@ func (_ *LocalFilesystemBlobAccess) GetType() string { return LocalFilesystemBlobType } -func (a LocalFilesystemBlobAccess) GetData() ([]byte, error) { - return json.Marshal(a) -} - -func (a *LocalFilesystemBlobAccess) SetData(bytes []byte) error { - var newAccess LocalFilesystemBlobAccess - if err := json.Unmarshal(bytes, &newAccess); err != nil { - return err - } - a.Filename = newAccess.Filename - return nil -} - // WebType is the type of a web component const WebType = "web" @@ -264,24 +188,10 @@ func (_ *Web) GetType() string { return WebType } -func (w Web) GetData() ([]byte, error) { - return yaml.Marshal(w) -} - -func (w *Web) SetData(bytes []byte) error { - var newWeb Web - if err := json.Unmarshal(bytes, &newWeb); err != nil { - return err - } - - w.URL = newWeb.URL - return nil -} - -// WebType is the type of a web component +// GitHubAccessType is the type of a git object. const GitHubAccessType = "github" -// GitHubAccess describes a github respository resource access. +// GitHubAccess describes a github repository resource access. type GitHubAccess struct { ObjectType `json:",inline"` @@ -309,18 +219,3 @@ func NewGitHubAccess(url, ref, commit string) TypedObjectAccessor { func (a GitHubAccess) GetType() string { return GitHubAccessType } - -func (a GitHubAccess) GetData() ([]byte, error) { - return yaml.Marshal(a) -} - -func (a *GitHubAccess) SetData(bytes []byte) error { - var newGitHubAccess GitHubAccess - if err := json.Unmarshal(bytes, &newGitHubAccess); err != nil { - return err - } - - a.RepoURL = newGitHubAccess.RepoURL - a.Ref = newGitHubAccess.Ref - return nil -} diff --git a/bindings-go/apis/v2/cdutils/resources.go b/bindings-go/apis/v2/cdutils/resources.go index 107df16e..c25cee2d 100644 --- a/bindings-go/apis/v2/cdutils/resources.go +++ b/bindings-go/apis/v2/cdutils/resources.go @@ -32,13 +32,8 @@ func GetImageReferenceByName(cd *cdv2.ComponentDescriptor, name string) (string, if res.Access.GetType() != cdv2.OCIRegistryType { return "", fmt.Errorf("resource is expected to be of type %q but is of type %q", cdv2.OCIRegistryType, res.Access.GetType()) } - - data, err := res.Access.GetData() - if err != nil { - return "", err - } ociImageAccess := &cdv2.OCIRegistryAccess{} - if err := cdv2.NewDefaultCodec().Decode(data, ociImageAccess); err != nil { + if err := res.Access.DecodeInto(ociImageAccess); err != nil { return "", err } return ociImageAccess.ImageReference, nil diff --git a/bindings-go/apis/v2/cdutils/utils.go b/bindings-go/apis/v2/cdutils/utils.go index 4ac84897..fbc8a756 100644 --- a/bindings-go/apis/v2/cdutils/utils.go +++ b/bindings-go/apis/v2/cdutils/utils.go @@ -6,7 +6,6 @@ package cdutils import ( "encoding/json" - "fmt" v2 "github.com/gardener/component-spec/bindings-go/apis/v2" ) @@ -121,29 +120,3 @@ func SetExtraIdentityField(o *v2.IdentityObjectMeta, key, val string) { } o.ExtraIdentity[key] = val } - -// ToUnstructuredTypedObject converts a typed object to a unstructured object. -func ToUnstructuredTypedObject(codec v2.TypedObjectCodec, obj v2.TypedObjectAccessor) (*v2.UnstructuredAccessType, error) { - data, err := codec.Encode(obj) - if err != nil { - return nil, err - } - - uObj := &v2.UnstructuredAccessType{} - if err := uObj.Decode(data, uObj); err != nil { - return nil, err - } - return uObj, nil -} - -// FromUnstructuredObject converts a unstructured object into a typed object. -func FromUnstructuredObject(codec v2.TypedObjectCodec, uObj *v2.UnstructuredAccessType, obj v2.TypedObjectAccessor) error { - data, err := uObj.GetData() - if err != nil { - return fmt.Errorf("unable to get data from unstructured object: %w", err) - } - if err := codec.Decode(data, obj); err != nil { - return fmt.Errorf("unable to decode object %q into %q: %w", uObj.GetType(), obj.GetType(), err) - } - return err -} diff --git a/bindings-go/apis/v2/codecs.go b/bindings-go/apis/v2/codecs.go index 6696fc75..57f198e3 100644 --- a/bindings-go/apis/v2/codecs.go +++ b/bindings-go/apis/v2/codecs.go @@ -17,7 +17,6 @@ package v2 import ( "encoding/json" "fmt" - "strings" ) // KnownTypeValidationFunc defines a function that can validate types. @@ -99,23 +98,10 @@ var _ TypedObjectEncoder = DefaultJSONTypedObjectEncoder{} // Encode is the Encode implementation of the TypedObjectEncoder interface. func (e DefaultJSONTypedObjectEncoder) Encode(obj TypedObjectAccessor) ([]byte, error) { - obj.SetType(obj.GetType()) // hardcord the correct type if the type was not correctly constructed. + obj.SetType(obj.GetType()) // hardcode the correct type if the type was not correctly constructed. return json.Marshal(obj) } -// ValidateAccessType validates that a type is known or of a generic type. -// todo: revisit; currently "x-" specifies a generic type -func ValidateAccessType(ttype string) error { - if _, ok := KnownAccessTypes[ttype]; ok { - return nil - } - - if !strings.HasPrefix(ttype, "x-") { - return fmt.Errorf("unknown non generic types %s", ttype) - } - return nil -} - type codec struct { knownTypes KnownTypes defaultCodec TypedObjectCodec @@ -189,3 +175,29 @@ func (c *codec) Encode(acc TypedObjectAccessor) ([]byte, error) { return codec.Encode(acc) } + +// ToUnstructuredTypedObject converts a typed object to a unstructured object. +func ToUnstructuredTypedObject(codec TypedObjectCodec, obj TypedObjectAccessor) (*UnstructuredTypedObject, error) { + data, err := codec.Encode(obj) + if err != nil { + return nil, err + } + + uObj := &UnstructuredTypedObject{} + if err := json.Unmarshal(data, uObj); err != nil { + return nil, err + } + return uObj, nil +} + +// FromUnstructuredObject converts a unstructured object into a typed object. +func FromUnstructuredObject(codec TypedObjectCodec, uObj *UnstructuredTypedObject, obj TypedObjectAccessor) error { + data, err := uObj.GetRaw() + if err != nil { + return fmt.Errorf("unable to get data from unstructured object: %w", err) + } + if err := codec.Decode(data, obj); err != nil { + return fmt.Errorf("unable to decode object %q into %q: %w", uObj.GetType(), obj.GetType(), err) + } + return err +} diff --git a/bindings-go/apis/v2/componentdescriptor.go b/bindings-go/apis/v2/componentdescriptor.go index abbbc80b..a42f95d5 100644 --- a/bindings-go/apis/v2/componentdescriptor.go +++ b/bindings-go/apis/v2/componentdescriptor.go @@ -15,7 +15,6 @@ package v2 import ( - "bytes" "encoding/json" "errors" ) @@ -81,7 +80,7 @@ type ComponentDescriptor struct { type ComponentSpec struct { ObjectMeta `json:",inline"` // RepositoryContexts defines the previous repositories of the component - RepositoryContexts []RepositoryContext `json:"repositoryContexts"` + RepositoryContexts []*UnstructuredTypedObject `json:"repositoryContexts"` // Provider defines the provider type of a component. // It can be external or internal. Provider ProviderType `json:"provider"` @@ -93,16 +92,6 @@ type ComponentSpec struct { Resources []Resource `json:"resources"` } -// RepositoryContext describes a repository context. -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type RepositoryContext struct { - // Type defines the type of the component repository to resolve references. - Type string `json:"type"` - // BaseURL is the base url of the repository to resolve components. - BaseURL string `json:"baseUrl"` -} - // ObjectMeta defines a object that is uniquely identified by its name and version. // +k8s:deepcopy-gen=true type ObjectMeta struct { @@ -321,131 +310,17 @@ type TypedObjectAccessor interface { GetType() string // SetType sets the type of the access object. SetType(ttype string) - // GetData returns the custom data of a component. - GetData() ([]byte, error) - // SetData sets the custom data of a component. - SetData([]byte) error -} - -// NewEmptyUnstructured creates a new typed object without additional data. -func NewEmptyUnstructured(ttype string) *UnstructuredAccessType { - return NewUnstructuredType(ttype, nil) -} - -// NewUnstructuredType creates a new unstructured typed object. -func NewUnstructuredType(ttype string, data map[string]interface{}) *UnstructuredAccessType { - unstr := &UnstructuredAccessType{} - unstr.Object = data - unstr.SetType(ttype) - return unstr -} - -// UnstructuredAccessType describes a generic access type. -// +k8s:openapi-gen=true -type UnstructuredAccessType struct { - ObjectType `json:",inline"` - Raw []byte `json:"-"` - Object map[string]interface{} `json:"object"` -} - -func (u *UnstructuredAccessType) SetType(ttype string) { - u.ObjectType.SetType(ttype) - if u.Object == nil { - u.Object = make(map[string]interface{}) - } - u.Object["type"] = ttype -} - -// DeepCopyInto is deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (u *UnstructuredAccessType) DeepCopyInto(out *UnstructuredAccessType) { - *out = *u - raw := make([]byte, len(u.Raw)) - copy(raw, u.Raw) - _ = out.SetData(raw) } -// DeepCopy is deepcopy function, copying the receiver, creating a new UnstructuredAccessType. -func (u *UnstructuredAccessType) DeepCopy() *UnstructuredAccessType { - if u == nil { - return nil - } - out := new(UnstructuredAccessType) - u.DeepCopyInto(out) - return out -} - -func (u *UnstructuredAccessType) Decode(data []byte, into TypedObjectAccessor) error { - uObj, ok := into.(*UnstructuredAccessType) - if !ok { - return errors.New("unable to decode data into non unstructured type") - } - - return json.Unmarshal(data, uObj) -} - -func (u *UnstructuredAccessType) Encode(acc TypedObjectAccessor) ([]byte, error) { - uObj, ok := acc.(*UnstructuredAccessType) - if !ok { - return nil, errors.New("unable to decode data into non unstructured type") - } - return json.Marshal(uObj) -} - -var _ TypedObjectCodec = &UnstructuredAccessType{} - -func (u UnstructuredAccessType) GetData() ([]byte, error) { - data, err := json.Marshal(u.Object) - if err != nil { - return nil, err - } - if n := bytes.Compare(data, u.Raw); n != 0 { - u.Raw = data - } - return u.Raw, nil -} - -func (u *UnstructuredAccessType) SetData(data []byte) error { - obj := map[string]interface{}{} - if err := json.Unmarshal(data, &obj); err != nil { - return err - } - u.Raw = data - u.Object = obj - return nil -} - -// UnmarshalJSON implements a custom json unmarshal method for a unstructured typed object. -func (u *UnstructuredAccessType) UnmarshalJSON(data []byte) error { - typedObj := ObjectType{} - if err := json.Unmarshal(data, &typedObj); err != nil { - return err - } - - obj := UnstructuredAccessType{ - ObjectType: typedObj, - } - if err := obj.SetData(data); err != nil { - return err - } - *u = obj - return nil -} - -// MarshalJSON implements a custom json unmarshal method for a unstructured type. -func (u *UnstructuredAccessType) MarshalJSON() ([]byte, error) { - data, err := json.Marshal(u.Object) - if err != nil { - return nil, err - } - return data, nil -} +// Repository is a specific type that indicated a typed repository object. +type Repository TypedObjectAccessor // Source is the definition of a component's source. // +k8s:deepcopy-gen=true // +k8s:openapi-gen=true type Source struct { IdentityObjectMeta `json:",inline"` - Access *UnstructuredAccessType `json:"access"` + Access *UnstructuredTypedObject `json:"access"` } // SourceRef defines a reference to a source @@ -476,7 +351,7 @@ type Resource struct { // Access describes the type specific method to // access the defined resource. - Access *UnstructuredAccessType `json:"access"` + Access *UnstructuredTypedObject `json:"access"` } // ComponentReference describes the reference to another component in the registry. diff --git a/bindings-go/apis/v2/default.go b/bindings-go/apis/v2/default.go index 4a160aca..407df9b9 100644 --- a/bindings-go/apis/v2/default.go +++ b/bindings-go/apis/v2/default.go @@ -17,7 +17,7 @@ package v2 // DefaultComponent applies defaults to a component func DefaultComponent(component *ComponentDescriptor) error { if component.RepositoryContexts == nil { - component.RepositoryContexts = make([]RepositoryContext, 0) + component.RepositoryContexts = make([]*UnstructuredTypedObject, 0) } if component.Sources == nil { component.Sources = make([]Source, 0) diff --git a/bindings-go/apis/v2/helper.go b/bindings-go/apis/v2/helper.go index fc4984aa..f53a2210 100644 --- a/bindings-go/apis/v2/helper.go +++ b/bindings-go/apis/v2/helper.go @@ -72,20 +72,25 @@ func NewNameSelector(name string) selector.Interface { } // GetEffectiveRepositoryContext returns the current active repository context. -func (c ComponentDescriptor) GetEffectiveRepositoryContext() RepositoryContext { +func (c ComponentDescriptor) GetEffectiveRepositoryContext() *UnstructuredTypedObject { if len(c.RepositoryContexts) == 0 { - return RepositoryContext{} + return nil } return c.RepositoryContexts[len(c.RepositoryContexts)-1] } // InjectRepositoryContext appends the given repository context to components descriptor repository history. // The context is not appended if the effective repository context already matches the current context. -func InjectRepositoryContext(cd *ComponentDescriptor, repoCtx RepositoryContext) { +func InjectRepositoryContext(cd *ComponentDescriptor, repoCtx TypedObjectAccessor) error { effective := cd.GetEffectiveRepositoryContext() - if repoCtx != effective { - cd.RepositoryContexts = append(cd.RepositoryContexts, repoCtx) + uRepoCtx, err := NewUnstructured(repoCtx) + if err != nil { + return err + } + if !UnstructuredTypesEqual(effective, &uRepoCtx) { + cd.RepositoryContexts = append(cd.RepositoryContexts, &uRepoCtx) } + return nil } // GetComponentReferences returns all component references that matches the given selectors. diff --git a/bindings-go/apis/v2/helper_test.go b/bindings-go/apis/v2/helper_test.go new file mode 100644 index 00000000..2562fa35 --- /dev/null +++ b/bindings-go/apis/v2/helper_test.go @@ -0,0 +1,42 @@ +// Copyright 2021 Copyright (c) 2021 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the LICENSE file. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package v2_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + v2 "github.com/gardener/component-spec/bindings-go/apis/v2" +) + +var _ = Describe("helper", func() { + + It("should inject a new repository context if none is defined", func() { + cd := &v2.ComponentDescriptor{} + Expect(v2.DefaultComponent(cd)).To(Succeed()) + + repoCtx := v2.NewOCIRegistryRepository("example.com", "") + Expect(v2.InjectRepositoryContext(cd, repoCtx)).To(Succeed()) + Expect(cd.RepositoryContexts).To(HaveLen(1)) + + Expect(v2.InjectRepositoryContext(cd, repoCtx)).To(Succeed()) + Expect(cd.RepositoryContexts).To(HaveLen(1)) + + repoCtx2 := v2.NewOCIRegistryRepository("example.com/dev", "") + Expect(v2.InjectRepositoryContext(cd, repoCtx2)).To(Succeed()) + Expect(cd.RepositoryContexts).To(HaveLen(2)) + }) + +}) diff --git a/bindings-go/apis/v2/jsonscheme/bindata.go b/bindings-go/apis/v2/jsonscheme/bindata.go index 298940c1..7cb2fbd9 100644 --- a/bindings-go/apis/v2/jsonscheme/bindata.go +++ b/bindings-go/apis/v2/jsonscheme/bindata.go @@ -92,7 +92,7 @@ func LanguageIndependentComponentDescriptorV2SchemaYaml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "../../../../language-independent/component-descriptor-v2-schema.yaml", size: 8687, mode: os.FileMode(420), modTime: time.Unix(1610547234, 0)} + info := bindataFileInfo{name: "../../../../language-independent/component-descriptor-v2-schema.yaml", size: 8687, mode: os.FileMode(420), modTime: time.Unix(1610727054, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/bindings-go/apis/v2/repositorytypes.go b/bindings-go/apis/v2/repositorytypes.go new file mode 100644 index 00000000..2c332a26 --- /dev/null +++ b/bindings-go/apis/v2/repositorytypes.go @@ -0,0 +1,52 @@ +// Copyright 2020 Copyright (c) 2020 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the LICENSE file. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package v2 + +// ComponentNameMapping describes the method that is used to map the "Component Name", "Component Version"-tuples +// to OCI Image References. +type ComponentNameMapping string + +const ( + OCIRegistryURLPathMapping ComponentNameMapping = "urlPath" + OCIRegistryDigestMapping ComponentNameMapping = "sha256-digest" +) + +// OCIRegistryRepository describes a component repository backed by a oci registry. +type OCIRegistryRepository struct { + ObjectType `json:",inline"` + // BaseURL is the base url of the repository to resolve components. + BaseURL string `json:"baseUrl"` + // ComponentNameMapping describes the method that is used to map the "Component Name", "Component Version"-tuples + // to OCI Image References. + ComponentNameMapping ComponentNameMapping `json:"componentNameMapping"` +} + +// NewOCIRegistryRepository creates a new OCIRegistryRepository accessor +func NewOCIRegistryRepository(baseURL string, mapping ComponentNameMapping) Repository { + if len(mapping) == 0 { + mapping = OCIRegistryURLPathMapping + } + return &OCIRegistryRepository{ + ObjectType: ObjectType{ + Type: OCIRegistryType, + }, + BaseURL: baseURL, + ComponentNameMapping: mapping, + } +} + +func (a *OCIRegistryRepository) GetType() string { + return OCIRegistryType +} diff --git a/bindings-go/apis/v2/unstructured.go b/bindings-go/apis/v2/unstructured.go new file mode 100644 index 00000000..b1b078b5 --- /dev/null +++ b/bindings-go/apis/v2/unstructured.go @@ -0,0 +1,166 @@ +// Copyright 2021 Copyright (c) 2021 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the LICENSE file. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package v2 + +import ( + "bytes" + "encoding/json" +) + +// UnstructuredTypesEqual compares two unstructured object. +func UnstructuredTypesEqual(a, b *UnstructuredTypedObject) bool { + if a == nil && b == nil { + return true + } + if a == nil || b == nil { + return false + } + if a.GetType() != b.GetType() { + return false + } + rawA, err := a.GetRaw() + if err != nil { + return false + } + rawB, err := b.GetRaw() + if err != nil { + return false + } + return bytes.Equal(rawA, rawB) +} + +// TypedObjectEqual compares two typed objects using the unstructured type. +func TypedObjectEqual(a, b TypedObjectAccessor) bool { + if a.GetType() != b.GetType() { + return false + } + uA, err := NewUnstructured(a) + if err != nil { + return false + } + uB, err := NewUnstructured(b) + if err != nil { + return false + } + return UnstructuredTypesEqual(&uA, &uB) +} + +// NewUnstructured creates a new unstructured object from a typed object using the default codec. +func NewUnstructured(obj TypedObjectAccessor) (UnstructuredTypedObject, error) { + uObj, err := ToUnstructuredTypedObject(NewDefaultCodec(), obj) + if err != nil { + return UnstructuredTypedObject{}, nil + } + return *uObj, nil +} + +// NewEmptyUnstructured creates a new typed object without additional data. +func NewEmptyUnstructured(ttype string) *UnstructuredTypedObject { + return NewUnstructuredType(ttype, nil) +} + +// NewUnstructuredType creates a new unstructured typed object. +func NewUnstructuredType(ttype string, data map[string]interface{}) *UnstructuredTypedObject { + unstr := &UnstructuredTypedObject{} + unstr.Object = data + unstr.SetType(ttype) + return unstr +} + +// UnstructuredTypedObject describes a generic typed object. +// +k8s:openapi-gen=true +type UnstructuredTypedObject struct { + ObjectType `json:",inline"` + Raw []byte `json:"-"` + Object map[string]interface{} `json:"object"` +} + +func (u *UnstructuredTypedObject) SetType(ttype string) { + u.ObjectType.SetType(ttype) + if u.Object == nil { + u.Object = make(map[string]interface{}) + } + u.Object["type"] = ttype +} + +// DeepCopyInto is deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (u *UnstructuredTypedObject) DeepCopyInto(out *UnstructuredTypedObject) { + *out = *u + raw := make([]byte, len(u.Raw)) + copy(raw, u.Raw) + _ = out.setRaw(raw) +} + +// DeepCopy is deepcopy function, copying the receiver, creating a new UnstructuredTypedObject. +func (u *UnstructuredTypedObject) DeepCopy() *UnstructuredTypedObject { + if u == nil { + return nil + } + out := new(UnstructuredTypedObject) + u.DeepCopyInto(out) + return out +} + +// DecodeInto decodes a unstructured typed object into a TypedObjectAccessor using the default codec +func (u *UnstructuredTypedObject) DecodeInto(into TypedObjectAccessor) error { + return FromUnstructuredObject(NewDefaultCodec(), u, into) +} + +func (u UnstructuredTypedObject) GetRaw() ([]byte, error) { + data, err := json.Marshal(u.Object) + if err != nil { + return nil, err + } + if !bytes.Equal(data, u.Raw) { + u.Raw = data + } + return u.Raw, nil +} + +func (u *UnstructuredTypedObject) setRaw(data []byte) error { + obj := map[string]interface{}{} + if err := json.Unmarshal(data, &obj); err != nil { + return err + } + u.Raw = data + u.Object = obj + return nil +} + +// UnmarshalJSON implements a custom json unmarshal method for a unstructured typed object. +func (u *UnstructuredTypedObject) UnmarshalJSON(data []byte) error { + typedObj := ObjectType{} + if err := json.Unmarshal(data, &typedObj); err != nil { + return err + } + + obj := UnstructuredTypedObject{ + ObjectType: typedObj, + } + if err := obj.setRaw(data); err != nil { + return err + } + *u = obj + return nil +} + +// MarshalJSON implements a custom json unmarshal method for a unstructured type. +func (u *UnstructuredTypedObject) MarshalJSON() ([]byte, error) { + data, err := json.Marshal(u.Object) + if err != nil { + return nil, err + } + return data, nil +} diff --git a/bindings-go/apis/v2/unstructured_test.go b/bindings-go/apis/v2/unstructured_test.go new file mode 100644 index 00000000..5944d4f7 --- /dev/null +++ b/bindings-go/apis/v2/unstructured_test.go @@ -0,0 +1,44 @@ +// Copyright 2021 Copyright (c) 2021 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the LICENSE file. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package v2_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + v2 "github.com/gardener/component-spec/bindings-go/apis/v2" +) + +var _ = Describe("UnstructuredTypedObject", func() { + + It("should create a new unstructured object using a typed accessor", func() { + type example struct { + v2.ObjectType + Test int `json:"test"` + } + obj := example{} + obj.Type = "example" + obj.Test = 3 + + uObj, err := v2.NewUnstructured(&obj) + Expect(err).ToNot(HaveOccurred()) + Expect(uObj.GetType()).To(Equal("example")) + + res := example{} + Expect(uObj.DecodeInto(&res)).To(Succeed()) + Expect(res).To(Equal(obj)) + }) + +}) diff --git a/bindings-go/apis/v2/v2_suite_test.go b/bindings-go/apis/v2/v2_suite_test.go index d14241bc..8355117e 100644 --- a/bindings-go/apis/v2/v2_suite_test.go +++ b/bindings-go/apis/v2/v2_suite_test.go @@ -21,7 +21,6 @@ import ( . "github.com/onsi/gomega" v2 "github.com/gardener/component-spec/bindings-go/apis/v2" - "github.com/gardener/component-spec/bindings-go/apis/v2/cdutils" ) func TestConfig(t *testing.T) { @@ -48,7 +47,7 @@ var _ = Describe("Helper", func() { ImageReference: "docker/image1:1.2.3", } - unstrucOCIRegistry1, err := cdutils.ToUnstructuredTypedObject(v2.NewCodec(nil, nil, nil), ociRegistry1) + unstrucOCIRegistry1, err := v2.NewUnstructured(ociRegistry1) Expect(err).ToNot(HaveOccurred()) ociImage1 = &v2.Resource{ @@ -61,7 +60,7 @@ var _ = Describe("Helper", func() { }, }, Relation: v2.ExternalRelation, - Access: unstrucOCIRegistry1, + Access: &unstrucOCIRegistry1, } ociRegistry2 = &v2.OCIRegistryAccess{ ObjectType: v2.ObjectType{ @@ -69,7 +68,7 @@ var _ = Describe("Helper", func() { }, ImageReference: "docker/image1:1.2.3", } - unstrucOCIRegistry2, err := cdutils.ToUnstructuredTypedObject(v2.NewCodec(nil, nil, nil), ociRegistry2) + unstrucOCIRegistry2, err := v2.NewUnstructured(ociRegistry2) Expect(err).ToNot(HaveOccurred()) ociImage2 = &v2.Resource{ IdentityObjectMeta: v2.IdentityObjectMeta{ @@ -81,7 +80,7 @@ var _ = Describe("Helper", func() { }, }, Relation: v2.ExternalRelation, - Access: unstrucOCIRegistry2, + Access: &unstrucOCIRegistry2, } comp = &v2.ComponentDescriptor{ diff --git a/bindings-go/apis/v2/validation/validation_test.go b/bindings-go/apis/v2/validation/validation_test.go index ed12a93e..62c59cb7 100644 --- a/bindings-go/apis/v2/validation/validation_test.go +++ b/bindings-go/apis/v2/validation/validation_test.go @@ -23,7 +23,6 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" v2 "github.com/gardener/component-spec/bindings-go/apis/v2" - "github.com/gardener/component-spec/bindings-go/apis/v2/cdutils" ) func TestConfig(t *testing.T) { @@ -50,7 +49,7 @@ var _ = Describe("Validation", func() { ImageReference: "docker/image1:1.2.3", } - unstrucOCIRegistry1, err := cdutils.ToUnstructuredTypedObject(v2.NewCodec(nil, nil, nil), ociRegistry1) + unstrucOCIRegistry1, err := v2.NewUnstructured(ociRegistry1) Expect(err).ToNot(HaveOccurred()) ociImage1 = &v2.Resource{ @@ -59,7 +58,7 @@ var _ = Describe("Validation", func() { Version: "1.2.3", }, Relation: v2.ExternalRelation, - Access: unstrucOCIRegistry1, + Access: &unstrucOCIRegistry1, } ociRegistry2 = &v2.OCIRegistryAccess{ ObjectType: v2.ObjectType{ @@ -67,7 +66,7 @@ var _ = Describe("Validation", func() { }, ImageReference: "docker/image1:1.2.3", } - unstrucOCIRegistry2, err := cdutils.ToUnstructuredTypedObject(v2.NewCodec(nil, nil, nil), ociRegistry2) + unstrucOCIRegistry2, err := v2.NewUnstructured(ociRegistry2) Expect(err).ToNot(HaveOccurred()) ociImage2 = &v2.Resource{ IdentityObjectMeta: v2.IdentityObjectMeta{ @@ -75,7 +74,7 @@ var _ = Describe("Validation", func() { Version: "1.2.3", }, Relation: v2.ExternalRelation, - Access: unstrucOCIRegistry2, + Access: &unstrucOCIRegistry2, } comp = &v2.ComponentDescriptor{ diff --git a/bindings-go/apis/v2/zz_generated_deepcopy.go b/bindings-go/apis/v2/zz_generated_deepcopy.go index fb652a8d..f2ae76d3 100644 --- a/bindings-go/apis/v2/zz_generated_deepcopy.go +++ b/bindings-go/apis/v2/zz_generated_deepcopy.go @@ -67,8 +67,13 @@ func (in *ComponentSpec) DeepCopyInto(out *ComponentSpec) { in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) if in.RepositoryContexts != nil { in, out := &in.RepositoryContexts, &out.RepositoryContexts - *out = make([]RepositoryContext, len(*in)) - copy(*out, *in) + *out = make([]*UnstructuredTypedObject, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = (*in).DeepCopy() + } + } } if in.Sources != nil { in, out := &in.Sources, &out.Sources @@ -254,22 +259,6 @@ func (in *ObjectType) DeepCopy() *ObjectType { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RepositoryContext) DeepCopyInto(out *RepositoryContext) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RepositoryContext. -func (in *RepositoryContext) DeepCopy() *RepositoryContext { - if in == nil { - return nil - } - out := new(RepositoryContext) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Resource) DeepCopyInto(out *Resource) { *out = *in diff --git a/bindings-go/ctf/componentarchive.go b/bindings-go/ctf/componentarchive.go index d19fb138..127b633c 100644 --- a/bindings-go/ctf/componentarchive.go +++ b/bindings-go/ctf/componentarchive.go @@ -33,7 +33,6 @@ import ( "github.com/opencontainers/go-digest" "github.com/gardener/component-spec/bindings-go/apis/v2" - "github.com/gardener/component-spec/bindings-go/apis/v2/cdutils" "github.com/gardener/component-spec/bindings-go/codec" ) @@ -163,11 +162,11 @@ func (ca *ComponentArchive) AddResource(res *v2.Resource, info BlobInfo, reader } localFsAccess := v2.NewLocalFilesystemBlobAccess(info.Digest, info.MediaType) - unstructuredType, err := cdutils.ToUnstructuredTypedObject(v2.NewCodec(nil, nil, nil), localFsAccess) + unstructuredType, err := v2.NewUnstructured(localFsAccess) if err != nil { return fmt.Errorf("unable to convert local filesystem type to untructured type: %w", err) } - res.Access = unstructuredType + res.Access = &unstructuredType if id == -1 { ca.ComponentDescriptor.Resources = append(ca.ComponentDescriptor.Resources, *res) @@ -206,11 +205,11 @@ func (ca *ComponentArchive) AddSource(src *v2.Source, info BlobInfo, reader io.R } localFsAccess := v2.NewLocalFilesystemBlobAccess(info.Digest, info.MediaType) - unstructuredType, err := cdutils.ToUnstructuredTypedObject(v2.NewCodec(nil, nil, nil), localFsAccess) + unstructuredType, err := v2.NewUnstructured(localFsAccess) if err != nil { return fmt.Errorf("unable to convert local filesystem type to untructured type: %w", err) } - src.Access = unstructuredType + src.Access = &unstructuredType if id == -1 { ca.ComponentDescriptor.Sources = append(ca.ComponentDescriptor.Sources, *src) @@ -254,11 +253,11 @@ func (ca *ComponentArchive) AddResourceFromResolver(ctx context.Context, res *v2 } localFsAccess := v2.NewLocalFilesystemBlobAccess(info.Digest, info.MediaType) - unstructuredType, err := cdutils.ToUnstructuredTypedObject(v2.NewCodec(nil, nil, nil), localFsAccess) + unstructuredType, err := v2.NewUnstructured(localFsAccess) if err != nil { return fmt.Errorf("unable to convert local filesystem type to untructured type: %w", err) } - res.Access = unstructuredType + res.Access = &unstructuredType if id == -1 { ca.ComponentDescriptor.Resources = append(ca.ComponentDescriptor.Resources, *res) diff --git a/bindings-go/ctf/ctf.go b/bindings-go/ctf/ctf.go index f1c4d829..9e52c42c 100644 --- a/bindings-go/ctf/ctf.go +++ b/bindings-go/ctf/ctf.go @@ -44,8 +44,8 @@ var UnsupportedResolveType = errors.New("UnsupportedResolveType") // ComponentResolver describes a general interface to resolve a component descriptor type ComponentResolver interface { - Resolve(ctx context.Context, repoCtx v2.RepositoryContext, name, version string) (*v2.ComponentDescriptor, error) - ResolveWithBlobResolver(ctx context.Context, repoCtx v2.RepositoryContext, name, version string) (*v2.ComponentDescriptor, BlobResolver, error) + Resolve(ctx context.Context, repoCtx v2.Repository, name, version string) (*v2.ComponentDescriptor, error) + ResolveWithBlobResolver(ctx context.Context, repoCtx v2.Repository, name, version string) (*v2.ComponentDescriptor, BlobResolver, error) } // BlobResolver defines a resolver that can fetch diff --git a/bindings-go/ctf/ctfutils/ctfutils.go b/bindings-go/ctf/ctfutils/ctfutils.go index 052318d0..c7a521dd 100644 --- a/bindings-go/ctf/ctfutils/ctfutils.go +++ b/bindings-go/ctf/ctfutils/ctfutils.go @@ -15,7 +15,7 @@ import ( // ResolveList resolves all component descriptors of a given root component descriptor. func ResolveList(ctx context.Context, resolver ctf.ComponentResolver, - repoCtx cdv2.RepositoryContext, + repoCtx cdv2.Repository, name, version string) (*cdv2.ComponentDescriptorList, error) { @@ -42,14 +42,14 @@ type ResolvedCallbackFunc func(descriptor *cdv2.ComponentDescriptor) (stop bool, // - the callback returns true for the stop parameter // - the callback returns an error // - all components are successfully resolved. -func ResolveRecursive(ctx context.Context, resolver ctf.ComponentResolver, repoCtx cdv2.RepositoryContext, name, version string, cb ResolvedCallbackFunc) error { +func ResolveRecursive(ctx context.Context, resolver ctf.ComponentResolver, repoCtx cdv2.Repository, name, version string, cb ResolvedCallbackFunc) error { cd, err := resolver.Resolve(ctx, repoCtx, name, version) if err != nil { - return fmt.Errorf("unable to resolve component descriptor for %q %q %q: %w", repoCtx.BaseURL, name, version, err) + return fmt.Errorf("unable to resolve component descriptor for %q %q %q: %w", repoCtx.GetType(), name, version, err) } stop, err := cb(cd) if err != nil { - return fmt.Errorf("error while calling callback for %q %q %q: %w", repoCtx.BaseURL, name, version, err) + return fmt.Errorf("error while calling callback for %q %q %q: %w", repoCtx.GetType(), name, version, err) } if stop { return nil @@ -57,16 +57,16 @@ func ResolveRecursive(ctx context.Context, resolver ctf.ComponentResolver, repoC return resolveRecursive(ctx, resolver, repoCtx, cd, cb) } -func resolveRecursive(ctx context.Context, resolver ctf.ComponentResolver, repoCtx cdv2.RepositoryContext, cd *cdv2.ComponentDescriptor, cb ResolvedCallbackFunc) error { +func resolveRecursive(ctx context.Context, resolver ctf.ComponentResolver, repoCtx cdv2.Repository, cd *cdv2.ComponentDescriptor, cb ResolvedCallbackFunc) error { components := make([]*cdv2.ComponentDescriptor, len(cd.ComponentReferences)) for _, ref := range cd.ComponentReferences { cd, err := resolver.Resolve(ctx, repoCtx, ref.ComponentName, ref.Version) if err != nil { - return fmt.Errorf("unable to resolve component descriptor for %q %q %q: %w", repoCtx.BaseURL, ref.ComponentName, ref.Version, err) + return fmt.Errorf("unable to resolve component descriptor for %q %q %q: %w", repoCtx.GetType(), ref.ComponentName, ref.Version, err) } stop, err := cb(cd) if err != nil { - return fmt.Errorf("error while calling callback for %q %q %q: %w", repoCtx.BaseURL, ref.ComponentName, ref.Version, err) + return fmt.Errorf("error while calling callback for %q %q %q: %w", repoCtx.GetType(), ref.ComponentName, ref.Version, err) } if stop { return nil diff --git a/bindings-go/examples/custom/main.go b/bindings-go/examples/custom/main.go index 0d637ef3..557a5948 100644 --- a/bindings-go/examples/custom/main.go +++ b/bindings-go/examples/custom/main.go @@ -15,8 +15,6 @@ package main import ( - "encoding/json" - "errors" "fmt" "os" @@ -24,6 +22,19 @@ import ( "github.com/gardener/component-spec/bindings-go/codec" ) +const NPMType = "npm" + +// NPMAccess defines a custom access type that specifies node npm module. +// +// By all access types are decoded with the JSONDecoder so the json annotation have to be defined on the type. +type NPMAccess struct { + v2.ObjectType + NodeModule string `json:"nodeModule"` + Version string `json:"version"` +} + +var _ v2.TypedObjectAccessor = &NPMAccess{} + func main() { data := []byte(` meta: @@ -38,7 +49,7 @@ component: componentReferences: [] resources: - - name: 'ftpRes' + - name: 'ftp-res' version: 'v1.7.2' type: 'custom1' relation: local @@ -46,7 +57,7 @@ component: type: 'x-ftp' url: ftp://example.com/my-resource - - name: 'nodeMod' + - name: 'node-mod' version: '0.0.1' type: 'nodeModule' relation: external @@ -60,21 +71,17 @@ component: err := codec.Decode(data, component) check(err) - res, err := component.GetLocalResource("custom1", "ftpRes", "v1.7.2") + res, err := component.GetLocalResource("custom1", "ftp-res", "v1.7.2") check(err) // by default all types are serialized as unstructured type ftpAccess := res.Access fmt.Println(ftpAccess.Object["url"]) // prints: ftp://example.com/my-resource - // By default unknown types are decoded with the JSONDecoder. - // A specific decoder can be registered by adding it to known types. - knownTypes := v2.KnownAccessTypes - knownTypes.Register(NPMType, npmCodec) - accessTypeCodec := v2.NewCodec(knownTypes, nil, nil) - res, err = component.GetExternalResource("nodeModule", "nodeMod", "0.0.1") + // By all types are decoded with the JSONDecoder. + res, err = component.GetExternalResource("nodeModule", "node-mod", "0.0.1") check(err) npmAccess := &NPMAccess{} - check(accessTypeCodec.Decode(res.Access.Raw, npmAccess)) + check(res.Access.DecodeInto(npmAccess)) fmt.Println(npmAccess.NodeModule) // prints: my-module } @@ -84,50 +91,3 @@ func check(err error) { os.Exit(1) } } - -const NPMType = "npm" - -type NPMAccess struct { - v2.ObjectType - NodeModule string `json:"nodeModule"` - Version string `json:"version"` -} - -var _ v2.TypedObjectAccessor = &NPMAccess{} - -func (n NPMAccess) GetData() ([]byte, error) { - return json.Marshal(n) -} - -func (n *NPMAccess) SetData(bytes []byte) error { - var newNPM NPMAccess - if err := json.Unmarshal(bytes, &newNPM); err != nil { - return err - } - - n.NodeModule = newNPM.NodeModule - n.Version = newNPM.Version - return nil -} - -var npmCodec = &v2.TypedObjectCodecWrapper{ - TypedObjectDecoder: v2.TypedObjectDecoderFunc(func(data []byte, into v2.TypedObjectAccessor) error { - obj, ok := into.(*NPMAccess) - if !ok { - return errors.New("undecodable type") - } - var npm NPMAccess - if err := json.Unmarshal(data, &npm); err != nil { - return err - } - *obj = npm - return nil - }), - TypedObjectEncoder: v2.TypedObjectEncoderFunc(func(accessor v2.TypedObjectAccessor) ([]byte, error) { - npm, ok := accessor.(*NPMAccess) - if !ok { - return nil, fmt.Errorf("accessor is not of type %s", NPMType) - } - return json.Marshal(npm) - }), -} diff --git a/bindings-go/examples/main.go b/bindings-go/examples/main.go index 29051b27..e2f10c6f 100644 --- a/bindings-go/examples/main.go +++ b/bindings-go/examples/main.go @@ -34,7 +34,9 @@ component: provider: internal - repositoryContexts: [] + repositoryContexts: + - type: ociRegistry + baseUrl: example.com sources: [] componentReferences: [] @@ -71,6 +73,23 @@ component: err := codec.Decode(data, component) check(err) + ///////////////////////////////// + // Repository Context + //////////////////////////////// + + // get the latest repository context. + // the context is returned as unstructured object (similar to the access types) as differnt repository types + // with different attributes are possible. + unstructuredRepoCtx := component.GetEffectiveRepositoryContext() + // decode the unstructured type into a specific type + ociRepo := &v2.OCIRegistryRepository{} + check(unstructuredRepoCtx.DecodeInto(ociRepo)) + fmt.Printf("%s\n", ociRepo.BaseURL) // prints "example.com" + + ///////////////////////////////// + // Resourcess + //////////////////////////////// + // get a specific local resource res, err := component.GetLocalResource(v2.OCIImageType, "apiserver", "v1.7.2") check(err) @@ -83,9 +102,8 @@ component: // get the access for a resource // specific access type can be decoded using the access type codec. - accessTypeCodec := v2.NewCodec(nil, nil, nil) ociAccess := &v2.OCIRegistryAccess{} - check(accessTypeCodec.Decode(res.Access.Raw, ociAccess)) + check(res.Access.DecodeInto(ociAccess)) fmt.Println(ociAccess.ImageReference) // prints: k8s.gcr.io/hyperkube:v1.16.4 ///////////////////////////////// diff --git a/bindings-go/go.mod b/bindings-go/go.mod index 3ffcec53..b479d3a1 100644 --- a/bindings-go/go.mod +++ b/bindings-go/go.mod @@ -5,7 +5,7 @@ go 1.14 require ( github.com/ghodss/yaml v1.0.0 github.com/go-logr/logr v0.4.0 - github.com/mandelsoft/vfs v0.0.0-20201002134249-3c471f64a4d1 + github.com/mandelsoft/vfs v0.0.0-20210530103237-5249dc39ce91 github.com/onsi/ginkgo v1.14.0 github.com/onsi/gomega v1.10.1 github.com/opencontainers/go-digest v1.0.0 diff --git a/bindings-go/go.sum b/bindings-go/go.sum index 3b2dbd61..0f52cda8 100644 --- a/bindings-go/go.sum +++ b/bindings-go/go.sum @@ -81,8 +81,8 @@ github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/mandelsoft/filepath v0.0.0-20200909114706-3df73d378d55 h1:mFdiUG86O2iW+iDEpZKXf64efMWO4JvDT+zN3znUGIc= github.com/mandelsoft/filepath v0.0.0-20200909114706-3df73d378d55/go.mod h1:n4xEiUD2HNHnn2w5ZKF0qgjDecHVCWAl5DxZ7+pcFU8= -github.com/mandelsoft/vfs v0.0.0-20201002134249-3c471f64a4d1 h1:M+7aO6jQSNEAQVcWt2VwKG7JTLqqrg2d7IXs4avz6gA= -github.com/mandelsoft/vfs v0.0.0-20201002134249-3c471f64a4d1/go.mod h1:74aV7kulg9C434HiI3zNALN79QHc9IZMN+SI4UdLn14= +github.com/mandelsoft/vfs v0.0.0-20210530103237-5249dc39ce91 h1:IW3qfn0AelV/4nLyVxFqZ5mJGTv7jGdqEWcFXiybLJs= +github.com/mandelsoft/vfs v0.0.0-20210530103237-5249dc39ce91/go.mod h1:74aV7kulg9C434HiI3zNALN79QHc9IZMN+SI4UdLn14= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -128,7 +128,6 @@ github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= @@ -141,7 +140,6 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= @@ -157,7 +155,6 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200806060901-a37d78b92225 h1:a5kp7Ohh+lqGCGHUBQdPwGHTJXKNhVVWp34F+ncDC9M= golang.org/x/sys v0.0.0-20200806060901-a37d78b92225/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -169,7 +166,6 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72 h1:bw9doJza/SFBEweII/rHQh338oozWyiFsBRHtrflcws= golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7 h1:EBZoQjiKKPaLbPrbpssUfuHtwM6KV/vb4U85g/cigFY= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= diff --git a/bindings-go/hack/generate-code.sh b/bindings-go/hack/generate-code.sh index 3a6564a6..ec06afa4 100755 --- a/bindings-go/hack/generate-code.sh +++ b/bindings-go/hack/generate-code.sh @@ -19,4 +19,7 @@ echo "> Installing deepcopy-gen" go install "${PROJECT_ROOT}"/vendor/k8s.io/code-generator/cmd/deepcopy-gen echo "> Generating deepcopy functions for Component Descriptor" -"${GOPATH}"/bin/deepcopy-gen -i "${PROJECT_ROOT}"/apis/v2 -O zz_generated_deepcopy --go-header-file "${PROJECT_ROOT}"/hack/boilerplate.go.txt \ No newline at end of file + +pushd ${PROJECT_ROOT} +"${GOPATH}"/bin/deepcopy-gen -i ./apis/v2 -O zz_generated_deepcopy --go-header-file ./hack/boilerplate.go.txt +popd \ No newline at end of file diff --git a/bindings-go/oci/manifest.go b/bindings-go/oci/manifest.go index 92f4d870..cfce3d27 100644 --- a/bindings-go/oci/manifest.go +++ b/bindings-go/oci/manifest.go @@ -29,7 +29,6 @@ import ( ocispecv1 "github.com/opencontainers/image-spec/specs-go/v1" v2 "github.com/gardener/component-spec/bindings-go/apis/v2" - "github.com/gardener/component-spec/bindings-go/apis/v2/cdutils" "github.com/gardener/component-spec/bindings-go/codec" "github.com/gardener/component-spec/bindings-go/ctf" ) @@ -60,7 +59,7 @@ func (b *ManifestBuilder) StorageType(storageType string) *ManifestBuilder { return b } -// BuildNewManifest creates a ocispec Manifest from a component descriptor. +// Build creates a ocispec Manifest from a component descriptor. func (b *ManifestBuilder) Build(ctx context.Context) (*ocispecv1.Manifest, error) { // default storage type if len(b.componentDescriptorStorageType) == 0 { @@ -181,11 +180,11 @@ func (b *ManifestBuilder) addLocalBlobs(ctx context.Context) ([]ocispecv1.Descri } ociBlobAccess := v2.NewLocalOCIBlobAccess(desc.Digest.String()) - unstructuredType, err := cdutils.ToUnstructuredTypedObject(v2.NewCodec(nil, nil, nil), ociBlobAccess) + unstructuredType, err := v2.NewUnstructured(ociBlobAccess) if err != nil { return nil, fmt.Errorf("unable to convert ociBlob to untructured type: %w", err) } - res.Access = unstructuredType + res.Access = &unstructuredType b.archive.ComponentDescriptor.Resources[i] = res blobDescriptors = append(blobDescriptors, desc) } diff --git a/bindings-go/oci/oci_suite_test.go b/bindings-go/oci/oci_suite_test.go index e252312b..d546c980 100644 --- a/bindings-go/oci/oci_suite_test.go +++ b/bindings-go/oci/oci_suite_test.go @@ -27,33 +27,43 @@ var _ = Describe("helper", func(){ Context("OCIRef", func() { It("should correctly parse a repository url without a protocol and a component", func() { - repoCtx := cdv2.RepositoryContext{BaseURL: "example.com"} + repoCtx := cdv2.OCIRegistryRepository{BaseURL: "example.com"} ref, err := oci.OCIRef(repoCtx, "somecomp", "v0.0.0") Expect(err).ToNot(HaveOccurred()) Expect(ref).To(Equal("example.com/component-descriptors/somecomp:v0.0.0")) }) It("should correctly parse a repository url with a protocol and a component", func() { - repoCtx := cdv2.RepositoryContext{BaseURL: "http://example.com"} + repoCtx := cdv2.OCIRegistryRepository{BaseURL: "http://example.com"} ref, err := oci.OCIRef(repoCtx, "somecomp", "v0.0.0") Expect(err).ToNot(HaveOccurred()) Expect(ref).To(Equal("example.com/component-descriptors/somecomp:v0.0.0")) }) It("should correctly parse a repository url without a protocol and a port and a component", func() { - repoCtx := cdv2.RepositoryContext{BaseURL: "example.com:443"} + repoCtx := cdv2.OCIRegistryRepository{BaseURL: "example.com:443"} ref, err := oci.OCIRef(repoCtx, "somecomp", "v0.0.0") Expect(err).ToNot(HaveOccurred()) Expect(ref).To(Equal("example.com:443/component-descriptors/somecomp:v0.0.0")) }) It("should correctly parse a repository url with a protocol and a port and a component", func() { - repoCtx := cdv2.RepositoryContext{BaseURL: "http://example.com:443"} + repoCtx := cdv2.OCIRegistryRepository{BaseURL: "http://example.com:443"} ref, err := oci.OCIRef(repoCtx, "somecomp", "v0.0.0") Expect(err).ToNot(HaveOccurred()) Expect(ref).To(Equal("example.com:443/component-descriptors/somecomp:v0.0.0")) }) + It("should correctly parse a repository url with a sha256-digest name mapping", func() { + repoCtx := cdv2.OCIRegistryRepository{ + BaseURL: "example.com:443", + ComponentNameMapping: cdv2.OCIRegistryDigestMapping, + } + ref, err := oci.OCIRef(repoCtx, "somecomp", "v0.0.0") + Expect(err).ToNot(HaveOccurred()) + Expect(ref).To(Equal("example.com:443/e9332cb39f32d0cea12252c5512f17e54fd850cf71faa5f5f0ef09056af01add:v0.0.0")) + }) + }) }) @@ -76,13 +86,13 @@ func (t testClient) Fetch(ctx context.Context, ref string, desc ocispecv1.Descri // testCache describes a test resolve cache. type testCache struct { - get func (ctx context.Context, repoCtx cdv2.RepositoryContext, name, version string) (*cdv2.ComponentDescriptor, error) + get func (ctx context.Context, repoCtx cdv2.OCIRegistryRepository, name, version string) (*cdv2.ComponentDescriptor, error) store func(ctx context.Context, descriptor *cdv2.ComponentDescriptor) error } var _ oci.Cache = &testCache{} -func (t testCache) Get(ctx context.Context, repoCtx cdv2.RepositoryContext, name, version string) (*cdv2.ComponentDescriptor, error) { +func (t testCache) Get(ctx context.Context, repoCtx cdv2.OCIRegistryRepository, name, version string) (*cdv2.ComponentDescriptor, error) { return t.get(ctx, repoCtx, name, version) } diff --git a/bindings-go/oci/resolve.go b/bindings-go/oci/resolve.go index 26ad59c0..b330529e 100644 --- a/bindings-go/oci/resolve.go +++ b/bindings-go/oci/resolve.go @@ -18,6 +18,8 @@ import ( "archive/tar" "bytes" "context" + "crypto/sha256" + "encoding/hex" "encoding/json" "errors" "fmt" @@ -31,7 +33,6 @@ import ( ocispecv1 "github.com/opencontainers/image-spec/specs-go/v1" v2 "github.com/gardener/component-spec/bindings-go/apis/v2" - "github.com/gardener/component-spec/bindings-go/apis/v2/cdutils" "github.com/gardener/component-spec/bindings-go/codec" "github.com/gardener/component-spec/bindings-go/ctf" "github.com/go-logr/logr" @@ -47,7 +48,7 @@ type Client interface { } // OCIRef generates the oci reference from the repository context and a component name and version. -func OCIRef(repoCtx v2.RepositoryContext, name, version string) (string, error) { +func OCIRef(repoCtx v2.OCIRegistryRepository, name, version string) (string, error) { baseUrl := repoCtx.BaseURL if !strings.Contains(baseUrl, "://") { // add dummy protocol to correctly parse the the url @@ -57,8 +58,19 @@ func OCIRef(repoCtx v2.RepositoryContext, name, version string) (string, error) if err != nil { return "", err } - ref := path.Join(u.Host, u.Path, ComponentDescriptorNamespace, name) - return fmt.Sprintf("%s:%s", ref, version), nil + + switch repoCtx.ComponentNameMapping { + case v2.OCIRegistryURLPathMapping, "": + ref := path.Join(u.Host, u.Path, ComponentDescriptorNamespace, name) + return fmt.Sprintf("%s:%s", ref, version), nil + case v2.OCIRegistryDigestMapping: + h := sha256.New() + _, _ = h.Write([]byte(name)) + ref := path.Join(u.Host, u.Path, hex.EncodeToString(h.Sum(nil))) + return fmt.Sprintf("%s:%s", ref, version), nil + default: + return "", fmt.Errorf("unknown component name mapping method %s", repoCtx.ComponentNameMapping) + } } // Cache describes a interface to cache component descriptors. @@ -67,7 +79,7 @@ func OCIRef(repoCtx v2.RepositoryContext, name, version string) (string, error) // The blob resolver might be added in the future. type Cache interface { // Get reads a component descriptor from the cache. - Get(ctx context.Context, repoCtx v2.RepositoryContext, name, version string) (*v2.ComponentDescriptor, error) + Get(ctx context.Context, repoCtx v2.OCIRegistryRepository, name, version string) (*v2.ComponentDescriptor, error) // Store stores a component descriptor in the cache. Store(ctx context.Context, descriptor *v2.ComponentDescriptor) error } @@ -103,28 +115,40 @@ func (r *Resolver) WithLog(log logr.Logger) *Resolver { } // Resolve resolves a component descriptor by name and version within the configured context. -func (r *Resolver) Resolve(ctx context.Context, repoCtx v2.RepositoryContext, name, version string) (*v2.ComponentDescriptor, error) { +func (r *Resolver) Resolve(ctx context.Context, repoCtx v2.Repository, name, version string) (*v2.ComponentDescriptor, error) { cd, _, err := r.resolve(ctx, repoCtx, name, version, false) return cd, err } // ResolveWithBlobResolver resolves a component descriptor by name and version within the configured context. // And it also returns a blob resolver to access the local artifacts. -func (r *Resolver) ResolveWithBlobResolver(ctx context.Context, repoCtx v2.RepositoryContext, name, version string) (*v2.ComponentDescriptor, ctf.BlobResolver, error) { +func (r *Resolver) ResolveWithBlobResolver(ctx context.Context, repoCtx v2.Repository, name, version string) (*v2.ComponentDescriptor, ctf.BlobResolver, error) { return r.resolve(ctx, repoCtx, name, version, true) } // resolve resolves a component descriptor by name and version within the configured context. // If withBlobResolver is false the returned blobresolver is always nil -func (r *Resolver) resolve(ctx context.Context, repoCtx v2.RepositoryContext, name, version string, withBlobResolver bool) (*v2.ComponentDescriptor, ctf.BlobResolver, error) { - log := r.log.WithValues("repoCtx", repoCtx.BaseURL, "name", name, "version", version) +func (r *Resolver) resolve(ctx context.Context, repoCtx v2.Repository, name, version string, withBlobResolver bool) (*v2.ComponentDescriptor, ctf.BlobResolver, error) { + log := r.log.WithValues("repoCtx", repoCtx.GetType(), "name", name, "version", version) + + var repo v2.OCIRegistryRepository + switch r := repoCtx.(type) { + case *v2.UnstructuredTypedObject: + if err := r.DecodeInto(&repo); err != nil { + return nil, nil, err + } + case *v2.OCIRegistryRepository: + repo = *r + default: + return nil, nil, fmt.Errorf("unknown repository context type %s", repoCtx.GetType()) + } if r.cache != nil { - cd, err := r.cache.Get(ctx, repoCtx, name, version) + cd, err := r.cache.Get(ctx, repo, name, version) if err != nil { log.Error(err, "unable to get component descriptor") } else { if withBlobResolver { - manifest, ref , err := r.fetchManifest(ctx, repoCtx, name, version) + manifest, ref , err := r.fetchManifest(ctx, repo, name, version) if err != nil { return nil, nil, err } @@ -134,7 +158,7 @@ func (r *Resolver) resolve(ctx context.Context, repoCtx v2.RepositoryContext, na } } - manifest, ref , err := r.fetchManifest(ctx, repoCtx, name, version) + manifest, ref , err := r.fetchManifest(ctx, repo, name, version) if err != nil { return nil, nil, err } @@ -170,7 +194,9 @@ func (r *Resolver) resolve(ctx context.Context, repoCtx v2.RepositoryContext, na if err := codec.Decode(componentDescriptorBytes, cd, r.decodeOpts...); err != nil { return nil, nil, fmt.Errorf("unable to decode component descriptor: %w", err) } - v2.InjectRepositoryContext(cd, repoCtx) + if err := v2.InjectRepositoryContext(cd, &repo); err != nil { + return nil, nil, err + } if r.cache != nil { if err := r.cache.Store(ctx, cd.DeepCopy()); err != nil { @@ -186,7 +212,7 @@ func (r *Resolver) resolve(ctx context.Context, repoCtx v2.RepositoryContext, na // fetchManifest fetches the oci manifest. // The manifest and the oci ref is returned. -func (r *Resolver) fetchManifest(ctx context.Context, repoCtx v2.RepositoryContext, name, version string) (*ocispecv1.Manifest, string, error) { +func (r *Resolver) fetchManifest(ctx context.Context, repoCtx v2.OCIRegistryRepository, name, version string) (*ocispecv1.Manifest, string, error) { if repoCtx.Type != v2.OCIRegistryType { return nil, "", fmt.Errorf("unsupported type %s expected %s", repoCtx.Type, v2.OCIRegistryType) } @@ -203,7 +229,7 @@ func (r *Resolver) fetchManifest(ctx context.Context, repoCtx v2.RepositoryConte } // ToComponentArchive creates a tar archive in the CTF (Cnudie Transport Format) from the given component descriptor. -func (r *Resolver) ToComponentArchive(ctx context.Context, repoCtx v2.RepositoryContext, name, version string, writer io.Writer) error { +func (r *Resolver) ToComponentArchive(ctx context.Context, repoCtx v2.Repository, name, version string, writer io.Writer) error { cd, blobresolver, err := r.ResolveWithBlobResolver(ctx, repoCtx, name, version) if err != nil { return err @@ -272,7 +298,7 @@ func (b *blobResolver) resolve(ctx context.Context, res v2.Resource, writer io.W switch res.Access.GetType() { case v2.LocalOCIBlobType: localOCIAccess := &v2.LocalOCIBlobAccess{} - if err := cdutils.FromUnstructuredObject(v2.NewDefaultCodec(), res.Access, localOCIAccess); err != nil { + if err := res.Access.DecodeInto(localOCIAccess); err != nil { return nil, err } @@ -294,7 +320,7 @@ func (b *blobResolver) resolve(ctx context.Context, res v2.Resource, writer io.W }, nil case v2.OCIBlobType: ociBlobAccess := &v2.OCIBlobAccess{} - if err := cdutils.FromUnstructuredObject(v2.NewDefaultCodec(), res.Access, ociBlobAccess); err != nil { + if err := res.Access.DecodeInto(ociBlobAccess); err != nil { return nil, err } diff --git a/bindings-go/oci/resolve_test.go b/bindings-go/oci/resolve_test.go index 6faf8ce8..d5b08ab3 100644 --- a/bindings-go/oci/resolve_test.go +++ b/bindings-go/oci/resolve_test.go @@ -66,18 +66,17 @@ var _ = Describe("resolve", func(){ } }, } - cd, err := oci.NewResolver(ociClient).Resolve(ctx, cdv2.RepositoryContext{ - Type: cdv2.OCIRegistryType, - BaseURL: "example.com", - }, "example.com/my-comp", "0.0.0") + cd, err := oci.NewResolver(ociClient).Resolve(ctx, cdv2.NewOCIRegistryRepository("example.com", ""), "example.com/my-comp", "0.0.0") Expect(err).ToNot(HaveOccurred()) - Expect(cd.GetEffectiveRepositoryContext().BaseURL).To(Equal("example.com"), "the repository context should be injected") + repoCtx := &cdv2.OCIRegistryRepository{} + Expect(cd.GetEffectiveRepositoryContext().DecodeInto(repoCtx)).To(Succeed()) + Expect(repoCtx.BaseURL).To(Equal("example.com"), "the repository context should be injected") }) It("should not fetch from the client of a cache is provided", func() { ctx := context.Background() ociCache := &testCache{ - get: func(ctx context.Context, repoCtx cdv2.RepositoryContext, name, version string) (*cdv2.ComponentDescriptor, error) { + get: func(ctx context.Context, repoCtx cdv2.OCIRegistryRepository, name, version string) (*cdv2.ComponentDescriptor, error) { return defaultComponentDescriptor("example.com/my-comp", "0.0.0"), nil }, store: func(ctx context.Context, descriptor *cdv2.ComponentDescriptor) error { @@ -95,10 +94,7 @@ var _ = Describe("resolve", func(){ return nil }, } - cd, err := oci.NewResolver(ociClient).WithCache(ociCache).Resolve(ctx, cdv2.RepositoryContext{ - Type: cdv2.OCIRegistryType, - BaseURL: "example.com", - }, "example.com/my-comp", "0.0.0") + cd, err := oci.NewResolver(ociClient).WithCache(ociCache).Resolve(ctx, cdv2.NewOCIRegistryRepository("example.com", ""), "example.com/my-comp", "0.0.0") Expect(err).ToNot(HaveOccurred()) Expect(cd.Name).To(Equal("example.com/my-comp")) }) @@ -107,7 +103,7 @@ var _ = Describe("resolve", func(){ ctx := context.Background() storeCalled := false ociCache := &testCache{ - get: func(ctx context.Context, repoCtx cdv2.RepositoryContext, name, version string) (*cdv2.ComponentDescriptor, error) { + get: func(ctx context.Context, repoCtx cdv2.OCIRegistryRepository, name, version string) (*cdv2.ComponentDescriptor, error) { return nil, errors.New("not found") }, store: func(ctx context.Context, descriptor *cdv2.ComponentDescriptor) error { @@ -154,12 +150,12 @@ var _ = Describe("resolve", func(){ } }, } - cd, err := oci.NewResolver(ociClient).WithCache(ociCache).Resolve(ctx, cdv2.RepositoryContext{ - Type: cdv2.OCIRegistryType, - BaseURL: "example.com", - }, "example.com/my-comp", "0.0.0") + cd, err := oci.NewResolver(ociClient).WithCache(ociCache).Resolve(ctx, cdv2.NewOCIRegistryRepository("example.com", ""), "example.com/my-comp", "0.0.0") Expect(err).ToNot(HaveOccurred()) - Expect(cd.GetEffectiveRepositoryContext().BaseURL).To(Equal("example.com"), "the repository context should be injected") + + repoCtx := &cdv2.OCIRegistryRepository{} + Expect(cd.GetEffectiveRepositoryContext().DecodeInto(repoCtx)).To(Succeed()) + Expect(repoCtx.BaseURL).To(Equal("example.com"), "the repository context should be injected") Expect(storeCalled).To(BeTrue(), "the cache store function should be called") })