Skip to content

Commit 926e831

Browse files
committed
Reconcile operatorsource type by pullng down operator manifest from
specified app registry server and making it available to the cluster
1 parent b688273 commit 926e831

File tree

220 files changed

+45782
-68
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

220 files changed

+45782
-68
lines changed

Gopkg.lock

+286-25
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Gopkg.toml

+4
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,7 @@ required = [
6060
[[constraint]]
6161
name = "github.com/operator-framework/operator-lifecycle-manager"
6262
version = "0.7.1"
63+
64+
[[constraint]]
65+
branch = "master"
66+
name = "github.com/operator-framework/go-appr"

cmd/marketplace-operator/main.go

+5-3
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,12 @@ func main() {
3131
if err != nil {
3232
logrus.Fatalf("failed to get watch namespace: %v", err)
3333
}
34-
resyncPeriod := time.Duration(5) * time.Second
35-
logrus.Infof("Watching %s, %s, %s", resource, catalogSourceConfigKind, namespace)
34+
3635
// No reason to resync until we implement updating of CatalogSourceConfig CRs
37-
sdk.Watch(resource, catalogSourceConfigKind, namespace, 0)
36+
resyncPeriod := time.Duration(0) * time.Second
37+
38+
logrus.Infof("Watching %s, %s, %s", resource, catalogSourceConfigKind, namespace)
39+
sdk.Watch(resource, catalogSourceConfigKind, namespace, resyncPeriod)
3840

3941
operatorSourceKind := "OperatorSource"
4042
logrus.Infof("Watching %s, %s, %s, %d", resource, operatorSourceKind, namespace, resyncPeriod)

deploy/operatorsource.cr.yaml

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
apiVersion: "marketplace.redhat.com/v1alpha1"
22
kind: "OperatorSource"
33
metadata:
4-
name: "operatorsource-example"
4+
name: "localhost"
5+
spec:
6+
type: appregistry
7+
endpoint: "http://localhost:5000/cnr"

pkg/apis/marketplace/v1alpha1/register.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ var (
1717
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
1818
AddToScheme = SchemeBuilder.AddToScheme
1919
// SchemeGroupVersion is the group version used to register these objects.
20-
SchemeGroupVersion = schema.GroupVersion{Group: groupName, Version: version}
20+
SchemeGroupVersion = schema.GroupVersion{Group: groupName, Version: version}
21+
OperatorSourceKind = "OperatorSource"
22+
CatalogSourceConfigKind = "CatalogSourceConfig"
2123
)
2224

2325
func init() {

pkg/apis/marketplace/v1alpha1/types.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,13 @@ type OperatorSource struct {
5454
}
5555

5656
type OperatorSourceSpec struct {
57-
// Fill me
57+
// Type of operator source
58+
Type string `json:"type"`
59+
60+
// Endpoint points to the URL from where operator manifests can be fetched
61+
Endpoint string `json:"endpoint"`
5862
}
63+
5964
type OperatorSourceStatus struct {
6065
// Fill me
6166
}

pkg/appregistry/adapter.go

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package appregistry
2+
3+
import (
4+
"bytes"
5+
6+
appr "github.com/operator-framework/go-appr/appregistry"
7+
appr_blobs "github.com/operator-framework/go-appr/appregistry/blobs"
8+
appr_package "github.com/operator-framework/go-appr/appregistry/package_appr"
9+
appr_models "github.com/operator-framework/go-appr/models"
10+
)
11+
12+
const (
13+
mediaType string = "helm"
14+
)
15+
16+
// This interface (internal to this package) encapsulates nitty gritty details of go-appr client bindings
17+
type apprApiAdapter interface {
18+
// ListPackages returns a list of package(s) available to the user
19+
ListPackages() (appr_models.Packages, error)
20+
21+
// GetPackageMetadata returns metadata associated with a given package
22+
GetPackageMetadata(namespace string, repository string, release string) (*appr_models.Package, error)
23+
24+
// DownloadOperatorManifest downloads the blob associated with a given digest that directly corresponds to a package release
25+
DownloadOperatorManifest(namespace string, repository string, digest string) ([]byte, error)
26+
}
27+
28+
type apprApiAdapterImpl struct {
29+
client *appr.Appregistry
30+
}
31+
32+
func (a *apprApiAdapterImpl) ListPackages() (appr_models.Packages, error) {
33+
params := appr_package.NewListPackagesParams()
34+
35+
packages, err := a.client.PackageAppr.ListPackages(params)
36+
if err != nil {
37+
return nil, err
38+
}
39+
40+
return packages.Payload, nil
41+
}
42+
43+
func (a *apprApiAdapterImpl) GetPackageMetadata(namespace string, repository string, release string) (*appr_models.Package, error) {
44+
params := appr_package.NewShowPackageParams().
45+
WithNamespace(namespace).
46+
WithPackage(repository).
47+
WithRelease(release).
48+
WithMediaType(mediaType)
49+
50+
pkg, err := a.client.PackageAppr.ShowPackage(params)
51+
if err != nil {
52+
return nil, err
53+
}
54+
55+
return pkg.Payload, nil
56+
}
57+
58+
func (a *apprApiAdapterImpl) DownloadOperatorManifest(namespace string, repository string, digest string) ([]byte, error) {
59+
params := appr_blobs.NewPullBlobParams().
60+
WithNamespace(namespace).
61+
WithPackage(repository).
62+
WithDigest(digest)
63+
64+
writer := &bytes.Buffer{}
65+
_, err := a.client.Blobs.PullBlob(params, writer)
66+
if err != nil {
67+
return nil, err
68+
}
69+
70+
return writer.Bytes(), nil
71+
}

pkg/appregistry/appregistry.go

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package appregistry
2+
3+
import (
4+
"net/url"
5+
6+
"github.com/go-openapi/runtime"
7+
httptransport "github.com/go-openapi/runtime/client"
8+
"github.com/go-openapi/strfmt"
9+
apprclient "github.com/operator-framework/go-appr/appregistry"
10+
)
11+
12+
// NewClientFactory return a factory which can be used to instantiate a new appregistry client
13+
func NewClientFactory() ClientFactory {
14+
return &factory{}
15+
}
16+
17+
type ClientFactory interface {
18+
// New returns a new instance of appregistry Client from given source and type
19+
New(sourceType, source string) (Client, error)
20+
}
21+
22+
// Client exposes the functionality of app registry server
23+
type Client interface {
24+
// RetrieveAll retrieves all visible packages from the given source
25+
RetrieveAll() ([]*OperatorMetadata, error)
26+
27+
// RetrieveOneretrieves a given package from the source
28+
RetrieveOne(name, release string) (*OperatorMetadata, error)
29+
}
30+
31+
type factory struct{}
32+
33+
func (f *factory) New(sourceType, source string) (Client, error) {
34+
u, err := url.Parse(source)
35+
if err != nil {
36+
return nil, err
37+
}
38+
39+
transport := httptransport.New(u.Host, u.Path, []string{u.Scheme})
40+
transport.Consumers["application/x-gzip"] = runtime.ByteStreamConsumer()
41+
c := apprclient.New(transport, strfmt.Default)
42+
43+
return &client{
44+
adapter: &apprApiAdapterImpl{client: c},
45+
decoder: &blobDecoderImpl{},
46+
unmarshaller: &blobUnmarshallerImpl{},
47+
}, nil
48+
}

pkg/appregistry/appregistry_test.go

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package appregistry_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
8+
"github.com/operator-framework/operator-marketplace/pkg/appregistry"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestRetrieveAll(t *testing.T) {
13+
factory := appregistry.NewClientFactory()
14+
15+
client, err := factory.New("appregistry", "http://localhost:5000/cnr")
16+
require.NoError(t, err)
17+
18+
packages, err := client.RetrieveAll()
19+
20+
assert.NoError(t, err)
21+
assert.NotNil(t, packages)
22+
}

pkg/appregistry/client.go

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package appregistry
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"strings"
7+
)
8+
9+
// OperatorMetadata encapsulates operator metadata and manifest assocated with a package
10+
type OperatorMetadata struct {
11+
// Namespace is the namespace in app registry server under which the package is hosted.
12+
Namespace string
13+
14+
// Repository is the repository name for the specified package in app registry
15+
Repository string
16+
17+
// Release represents the release or version number of the given package
18+
Release string
19+
20+
// Digest is the sha256 hash value that uniquely corresponds to the blob associated with the release
21+
Digest string
22+
23+
// Manifest encapsulates operator manifest
24+
Manifest *Manifest
25+
}
26+
27+
func (om *OperatorMetadata) ID() string {
28+
return fmt.Sprintf("%s/%s", om.Namespace, om.Repository)
29+
}
30+
31+
type client struct {
32+
adapter apprApiAdapter
33+
decoder blobDecoder
34+
unmarshaller blobUnmarshaller
35+
}
36+
37+
func (c *client) RetrieveAll() ([]*OperatorMetadata, error) {
38+
packages, err := c.adapter.ListPackages()
39+
if err != nil {
40+
return nil, err
41+
}
42+
43+
list := make([]*OperatorMetadata, len(packages))
44+
for i, pkg := range packages {
45+
manifest, err := c.RetrieveOne(pkg.Name, pkg.Default)
46+
if err != nil {
47+
return nil, err
48+
}
49+
50+
list[i] = manifest
51+
}
52+
53+
return list, nil
54+
}
55+
56+
func (c *client) RetrieveOne(name, release string) (*OperatorMetadata, error) {
57+
namespace, repository, err := split(name)
58+
if err != nil {
59+
return nil, err
60+
}
61+
62+
metadata, err := c.adapter.GetPackageMetadata(namespace, repository, release)
63+
if err != nil {
64+
return nil, err
65+
}
66+
67+
digest := metadata.Content.Digest
68+
blob, err := c.adapter.DownloadOperatorManifest(namespace, repository, digest)
69+
if err != nil {
70+
return nil, err
71+
}
72+
73+
decoded, err := c.decoder.Decode(blob)
74+
if err != nil {
75+
return nil, err
76+
}
77+
78+
manifest, err := c.unmarshaller.Unmarshal(decoded)
79+
if err != nil {
80+
return nil, err
81+
}
82+
83+
om := &OperatorMetadata{
84+
Namespace: namespace,
85+
Repository: repository,
86+
Release: release,
87+
Manifest: manifest,
88+
Digest: digest,
89+
}
90+
91+
return om, nil
92+
}
93+
94+
func split(name string) (namespace string, repository string, err error) {
95+
// we expect package name to comply to this format - {namespace}/{repository}
96+
split := strings.Split(name, "/")
97+
if len(split) != 2 {
98+
return "", "", errors.New(fmt.Sprintf("package name should be specified in this format {namespace}/{repository}"))
99+
}
100+
101+
namespace = split[0]
102+
repository = split[1]
103+
104+
return namespace, repository, nil
105+
}

pkg/appregistry/client_test.go

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package appregistry
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
9+
"github.com/golang/mock/gomock"
10+
appr_models "github.com/operator-framework/go-appr/models"
11+
)
12+
13+
func TestRetrieveOne_PackageExists_SuccessExpected(t *testing.T) {
14+
controller := gomock.NewController(t)
15+
defer controller.Finish()
16+
17+
adapter := NewMockapprApiAdapter(controller)
18+
decoder := NewMockblobDecoder(controller)
19+
unmarshaller := NewMockblobUnmarshaller(controller)
20+
21+
client := client{
22+
adapter: adapter,
23+
decoder: decoder,
24+
unmarshaller: unmarshaller,
25+
}
26+
27+
namespace := "redhat"
28+
repository := "foo"
29+
release := "1.0"
30+
digest := "abcdefgh"
31+
32+
pkg := &appr_models.Package{Content: &appr_models.OciDescriptor{
33+
Digest: digest,
34+
}}
35+
adapter.EXPECT().GetPackageMetadata(namespace, repository, release).Return(pkg, nil).Times(1)
36+
37+
blobExpected := []byte{'e', 'n', 'c', 'o', 'd', 'e', 'd'}
38+
adapter.EXPECT().DownloadOperatorManifest(namespace, repository, digest).Return(blobExpected, nil).Times(1)
39+
40+
decodedExpected := []byte{'d', 'e', 'c', 'o', 'd', 'e', 'd'}
41+
decoder.EXPECT().Decode(blobExpected).Return(decodedExpected, nil).Times(1)
42+
43+
manifestExpected := &Manifest{
44+
Publisher: "redhat",
45+
Data: Data{
46+
CRDs: "my crds",
47+
CSVs: "my csvs",
48+
Packages: "my packages",
49+
},
50+
}
51+
unmarshaller.EXPECT().Unmarshal(decodedExpected).Return(manifestExpected, nil)
52+
53+
metadata, err := client.RetrieveOne(fmt.Sprintf("%s/%s", namespace, repository), release)
54+
55+
assert.NoError(t, err)
56+
assert.Equal(t, namespace, metadata.Namespace)
57+
assert.Equal(t, repository, metadata.Repository)
58+
assert.Equal(t, release, metadata.Release)
59+
assert.Equal(t, digest, metadata.Digest)
60+
assert.Equal(t, manifestExpected, metadata.Manifest)
61+
}

0 commit comments

Comments
 (0)