Skip to content

Commit 615435b

Browse files
committedFeb 14, 2019
Add appregistry-server binary
Add a new binary 'appregistry-server' that can be used to download operator manifest(s) from a remote app registry and generate a sqlite database to be served using grpc.
1 parent 4dd6f73 commit 615435b

34 files changed

+2675
-3
lines changed
 

‎cmd/appregistry-server/main.go

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package main
2+
3+
import (
4+
"net"
5+
6+
"github.com/sirupsen/logrus"
7+
"github.com/spf13/cobra"
8+
"google.golang.org/grpc"
9+
"google.golang.org/grpc/reflection"
10+
11+
"github.com/operator-framework/operator-registry/pkg/api"
12+
health "github.com/operator-framework/operator-registry/pkg/api/grpc_health_v1"
13+
"github.com/operator-framework/operator-registry/pkg/appregistry"
14+
"github.com/operator-framework/operator-registry/pkg/lib/log"
15+
"github.com/operator-framework/operator-registry/pkg/server"
16+
)
17+
18+
func main() {
19+
var rootCmd = &cobra.Command{
20+
Short: "appregistry-server",
21+
Long: `appregistry-server downloads operator manifest(s) from remote appregistry, builds a sqlite database containing these downloaded manifest(s) and serves a grpc API to query it`,
22+
23+
PreRunE: func(cmd *cobra.Command, args []string) error {
24+
if debug, _ := cmd.Flags().GetBool("debug"); debug {
25+
logrus.SetLevel(logrus.DebugLevel)
26+
}
27+
return nil
28+
},
29+
30+
RunE: runCmdFunc,
31+
}
32+
33+
rootCmd.Flags().Bool("debug", false, "enable debug logging")
34+
rootCmd.Flags().StringP("kubeconfig", "k", "", "absolute path to kubeconfig file")
35+
rootCmd.Flags().StringP("database", "d", "bundles.db", "name of db to output")
36+
rootCmd.Flags().StringP("sources", "s", "", "comma separated list of OperatorSource object(s) {namespace}/{name}")
37+
rootCmd.Flags().StringP("packages", "o", "", "comma separated list of package(s) to be downloaded from the specified operator source(s)")
38+
rootCmd.Flags().StringP("port", "p", "50051", "port number to serve on")
39+
rootCmd.Flags().StringP("termination-log", "t", "/dev/termination-log", "path to a container termination log file")
40+
if err := rootCmd.Flags().MarkHidden("debug"); err != nil {
41+
logrus.Panic(err.Error())
42+
}
43+
44+
if err := rootCmd.Execute(); err != nil {
45+
logrus.Panic(err.Error())
46+
}
47+
}
48+
49+
func runCmdFunc(cmd *cobra.Command, args []string) error {
50+
// Immediately set up termination log
51+
terminationLogPath, err := cmd.Flags().GetString("termination-log")
52+
if err != nil {
53+
return err
54+
}
55+
err = log.AddDefaultWriterHooks(terminationLogPath)
56+
if err != nil {
57+
return err
58+
}
59+
kubeconfig, err := cmd.Flags().GetString("kubeconfig")
60+
if err != nil {
61+
return err
62+
}
63+
port, err := cmd.Flags().GetString("port")
64+
if err != nil {
65+
return err
66+
}
67+
sources, err := cmd.Flags().GetString("sources")
68+
if err != nil {
69+
return err
70+
}
71+
packages, err := cmd.Flags().GetString("packages")
72+
if err != nil {
73+
return err
74+
}
75+
dbName, err := cmd.Flags().GetString("database")
76+
if err != nil {
77+
return err
78+
}
79+
80+
logger := logrus.WithFields(logrus.Fields{"type": "appregistry", "port": port})
81+
82+
loader, err := appregistry.NewLoader(kubeconfig, logger)
83+
if err != nil {
84+
logger.Fatalf("error initializing - %v", err)
85+
}
86+
87+
store, err := loader.Load(dbName, sources, packages)
88+
if err != nil {
89+
logger.Fatalf("error loading manifest from remote registry - %v", err)
90+
}
91+
92+
lis, err := net.Listen("tcp", ":"+port)
93+
if err != nil {
94+
logger.Fatalf("failed to listen: %v", err)
95+
}
96+
s := grpc.NewServer()
97+
98+
api.RegisterRegistryServer(s, server.NewRegistryServer(store))
99+
health.RegisterHealthServer(s, server.NewHealthServer())
100+
reflection.Register(s)
101+
102+
logger.Info("serving registry")
103+
if err := s.Serve(lis); err != nil {
104+
logger.Fatalf("failed to serve: %v", err)
105+
}
106+
107+
return nil
108+
}

‎go.mod

+2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ require (
2828
github.com/modern-go/reflect2 v1.0.1 // indirect
2929
github.com/operator-framework/go-appr v0.0.0-20180917210448-f2aef88446f2
3030
github.com/operator-framework/operator-lifecycle-manager v0.0.0-20190125151539-1e295784b30a
31+
github.com/operator-framework/operator-marketplace v0.0.0-20190212161948-a7ca81b96ad9
3132
github.com/sirupsen/logrus v1.2.0
3233
github.com/soheilhy/cmux v0.1.4 // indirect
3334
github.com/spf13/cobra v0.0.3
@@ -42,4 +43,5 @@ require (
4243
k8s.io/apiextensions-apiserver v0.0.0-20181204003618-e419c5771cdc
4344
k8s.io/apimachinery v0.0.0-20190118094746-1525e4dadd2d
4445
k8s.io/client-go v8.0.0+incompatible
46+
sigs.k8s.io/controller-runtime v0.1.10 // indirect
4547
)

‎go.sum

+6
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@ github.com/operator-framework/operator-lifecycle-manager v0.0.0-20181023032605-e
137137
github.com/operator-framework/operator-lifecycle-manager v0.0.0-20190105193533-81104ffdc4fb/go.mod h1:XMyE4n2opUK4N6L45YGQkXXi8F9fD7XDYFv/CsS6V5I=
138138
github.com/operator-framework/operator-lifecycle-manager v0.0.0-20190125151539-1e295784b30a h1:gBTaGobFQZVx+M2ItXxJp7oxoc9dltkLMrkgyDE0Qfc=
139139
github.com/operator-framework/operator-lifecycle-manager v0.0.0-20190125151539-1e295784b30a/go.mod h1:vq6TTFvg6ti1Bn6ACsZneZTmjTsURgDD6tQtVDbEgsU=
140+
github.com/operator-framework/operator-marketplace v0.0.0-20190208230340-d06f7b349013 h1:SyVL/xPGF6ALLDOQNchwagHz7QvESdgiiDyNoIRnMxI=
141+
github.com/operator-framework/operator-marketplace v0.0.0-20190208230340-d06f7b349013/go.mod h1:msZSL8pXwzQjB+hU+awVrZQw94IwJi3sNZVD3NoESIs=
142+
github.com/operator-framework/operator-marketplace v0.0.0-20190212161948-a7ca81b96ad9 h1:VjGYvB+9cqsf0vgO7npB1bwAIslvLFqqL1ydX9ogCRM=
143+
github.com/operator-framework/operator-marketplace v0.0.0-20190212161948-a7ca81b96ad9/go.mod h1:msZSL8pXwzQjB+hU+awVrZQw94IwJi3sNZVD3NoESIs=
140144
github.com/operator-framework/operator-registry v1.0.1/go.mod h1:1xEdZjjUg2hPEd52LG3YQ0jtwiwEGdm98S1TH5P4RAA=
141145
github.com/operator-framework/operator-registry v1.0.4/go.mod h1:hve6YwcjM2nGVlscLtNsp9sIIBkNZo6jlJgzWw7vP9s=
142146
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
@@ -249,3 +253,5 @@ k8s.io/kube-openapi v0.0.0-20181031203759-72693cb1fadd h1:ggv/Vfza0i5xuhUZyYyxcc
249253
k8s.io/kube-openapi v0.0.0-20181031203759-72693cb1fadd/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc=
250254
k8s.io/kubernetes v1.11.7-beta.0.0.20181219023948-b875d52ea96d/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
251255
k8s.io/kubernetes v1.11.8-beta.0.0.20190124204751-3a10094374f2/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
256+
sigs.k8s.io/controller-runtime v0.1.10 h1:amLOmcekVdnsD1uIpmgRqfTbQWJ2qxvQkcdeFhcotn4=
257+
sigs.k8s.io/controller-runtime v0.1.10/go.mod h1:HFAYoOh6XMV+jKF1UjFwrknPbowfyHEHHRdJMf2jMX8=

‎pkg/appregistry/appregistry.go

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package appregistry
2+
3+
import (
4+
"fmt"
5+
6+
marketplace "github.com/operator-framework/operator-marketplace/pkg/client/clientset/versioned"
7+
"github.com/operator-framework/operator-registry/pkg/sqlite"
8+
"github.com/sirupsen/logrus"
9+
"k8s.io/client-go/rest"
10+
"k8s.io/client-go/tools/clientcmd"
11+
)
12+
13+
func NewLoader(kubeconfig string, logger *logrus.Entry) (*AppregistryLoader, error) {
14+
client, err := NewClient(kubeconfig, logger)
15+
if err != nil {
16+
return nil, err
17+
}
18+
19+
return &AppregistryLoader{
20+
logger: logger,
21+
input: &inputParser{},
22+
downloader: &downloader{
23+
logger: logger,
24+
client: client,
25+
},
26+
merger: &merger{
27+
logger: logger,
28+
parser: &manifestYAMLParser{},
29+
},
30+
loader: &dbLoader{
31+
logger: logger,
32+
},
33+
}, nil
34+
}
35+
36+
type AppregistryLoader struct {
37+
logger *logrus.Entry
38+
input *inputParser
39+
downloader *downloader
40+
merger *merger
41+
loader *dbLoader
42+
}
43+
44+
func (a *AppregistryLoader) Load(dbName string, csvSources string, csvPackages string) (store *sqlite.SQLQuerier, err error) {
45+
a.logger.Infof("operator source(s) specified are - %s", csvSources)
46+
a.logger.Infof("package(s) specified are - %s", csvPackages)
47+
48+
input, err := a.input.Parse(csvSources, csvPackages)
49+
if err != nil {
50+
return nil, err
51+
}
52+
53+
a.logger.Infof("input sanitized - sources: %s, packages: %s", input.Sources, input.Packages)
54+
55+
rawManifests, err := a.downloader.Download(input)
56+
if err != nil {
57+
a.logger.Errorf("The following error occurred while downloading - %v", err)
58+
59+
if len(rawManifests) == 0 {
60+
a.logger.Info("No package manifest downloaded")
61+
return nil, err
62+
}
63+
}
64+
65+
a.logger.Infof("download complete - %d repositories have been downloaded", len(rawManifests))
66+
67+
data, err := a.merger.Merge(rawManifests)
68+
if err != nil {
69+
a.logger.Errorf("The following error occurred while processing manifest - %v", err)
70+
71+
if data == nil {
72+
a.logger.Info("No operator manifest bundled")
73+
return nil, err
74+
}
75+
}
76+
77+
a.logger.Info("all manifest(s) have been merged into one")
78+
a.logger.Info("loading into sqlite database")
79+
80+
store, err = a.loader.LoadToSQLite(dbName, data)
81+
return
82+
}
83+
84+
func NewClient(kubeconfig string, logger *logrus.Entry) (clientset marketplace.Interface, err error) {
85+
var config *rest.Config
86+
87+
if kubeconfig != "" {
88+
logger.Infof("Loading kube client config from path %q", kubeconfig)
89+
config, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
90+
} else {
91+
logger.Infof("Using in-cluster kube client config")
92+
config, err = rest.InClusterConfig()
93+
}
94+
95+
if err != nil {
96+
err = fmt.Errorf("Cannot load config for REST client: %v", err)
97+
return
98+
}
99+
100+
clientset, err = marketplace.NewForConfig(config)
101+
return
102+
}

‎pkg/appregistry/crd.go

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package appregistry
2+
3+
import (
4+
"fmt"
5+
6+
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
7+
)
8+
9+
// CRDKey contains metadata needed to uniquely identify a CRD.
10+
//
11+
// OLM uses CRDKey to uniquely identify a CustomResourceDefinition object. We
12+
// are following the same pattern to be consistent.
13+
type CRDKey struct {
14+
Kind string `json:"kind"`
15+
Name string `json:"name"`
16+
Version string `json:"version"`
17+
}
18+
19+
// String returns a string representation of this CRDKey object with Kind, Name
20+
// and Version concatenated.
21+
//
22+
// CRDKey is used as the key to map of CustomResourceDefinition object(s). This
23+
// function ensures that Kind, Name and Version are taken into account
24+
// to compute the key associated with a CustomResourceDefinition object.
25+
func (k CRDKey) String() string {
26+
return fmt.Sprintf("%s/%s/%s", k.Kind, k.Name, k.Version)
27+
}
28+
29+
// CustomResourceDefinition is a structured representation of custom resource
30+
// definition(s) specified in `customResourceDefinitions` section of an
31+
// operator manifest.
32+
type CustomResourceDefinition struct {
33+
v1beta1.CustomResourceDefinition `json:",inline"`
34+
}
35+
36+
// Key returns an instance of CRDKey which uniquely identifies a given
37+
// CustomResourceDefinition object.
38+
func (crd *CustomResourceDefinition) Key() CRDKey {
39+
return CRDKey{
40+
Kind: crd.Spec.Names.Kind,
41+
Name: crd.GetName(),
42+
Version: crd.Spec.Version,
43+
}
44+
}

‎pkg/appregistry/csv.go

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package appregistry
2+
3+
import (
4+
"encoding/json"
5+
6+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
7+
)
8+
9+
const (
10+
// Name of the section under which the list of owned and required list of
11+
// CRD(s) is specified inside an operator manifest.
12+
customResourceDefinitions = "customresourcedefinitions"
13+
14+
// The yaml attribute that points to the name of an older
15+
// ClusterServiceVersion object that the current ClusterServiceVersion
16+
// replaces.
17+
replaces = "replaces"
18+
)
19+
20+
// ClusterServiceVersion is a structured representation of cluster service
21+
// version object(s) specified inside the 'clusterServiceVersions' section of
22+
// an operator manifest.
23+
type ClusterServiceVersion struct {
24+
// Type metadata.
25+
metav1.TypeMeta `json:",inline"`
26+
27+
// Object metadata.
28+
metav1.ObjectMeta `json:"metadata"`
29+
30+
// Spec is the raw representation of the 'spec' element of
31+
// ClusterServiceVersion object. Since we are
32+
// not interested in the content of spec we are not parsing it.
33+
Spec json.RawMessage `json:"spec"`
34+
}
35+
36+
// GetReplaces returns the name of the older ClusterServiceVersion object that
37+
// is replaced by this ClusterServiceVersion object.
38+
//
39+
// If not defined, the function returns an empty string.
40+
func (csv *ClusterServiceVersion) GetReplaces() (string, error) {
41+
var objmap map[string]*json.RawMessage
42+
if err := json.Unmarshal(csv.Spec, &objmap); err != nil {
43+
return "", err
44+
}
45+
46+
rawValue, ok := objmap[replaces]
47+
if !ok || rawValue == nil {
48+
return "", nil
49+
}
50+
51+
var replaces string
52+
if err := json.Unmarshal(*rawValue, &replaces); err != nil {
53+
return "", err
54+
}
55+
56+
return replaces, nil
57+
}
58+
59+
// GetCustomResourceDefintions returns a list of owned and required
60+
// CustomResourceDefinition object(s) specified inside the
61+
// 'customresourcedefinitions' section of a ClusterServiceVersion 'spec'.
62+
//
63+
// owned represents the list of CRD(s) managed by this ClusterServiceVersion
64+
// object.
65+
// required represents the list of CRD(s) that this ClusterServiceVersion
66+
// object depends on.
67+
//
68+
// If owned or required is not defined in the spec then an empty list is
69+
// returned respectively.
70+
func (csv *ClusterServiceVersion) GetCustomResourceDefintions() (owned []*CRDKey, required []*CRDKey, err error) {
71+
var objmap map[string]*json.RawMessage
72+
73+
if err = json.Unmarshal(csv.Spec, &objmap); err != nil {
74+
return
75+
}
76+
77+
rawValue, ok := objmap[customResourceDefinitions]
78+
if !ok || rawValue == nil {
79+
return
80+
}
81+
82+
var definitions struct {
83+
Owned []*CRDKey `json:"owned"`
84+
Required []*CRDKey `json:"required"`
85+
}
86+
87+
if err = json.Unmarshal(*rawValue, &definitions); err != nil {
88+
return
89+
}
90+
91+
owned = definitions.Owned
92+
required = definitions.Required
93+
return
94+
}

‎pkg/appregistry/dbloader.go

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package appregistry
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/operator-framework/operator-registry/pkg/sqlite"
8+
"github.com/sirupsen/logrus"
9+
)
10+
11+
type dbLoader struct {
12+
logger *logrus.Entry
13+
}
14+
15+
// LoadToSQLite uses configMaploader to load the downloaded operator manifest(s)
16+
// into a sqllite database.
17+
func (l *dbLoader) LoadToSQLite(dbName string, manifest *RawOperatorManifestData) (store *sqlite.SQLQuerier, err error) {
18+
l.logger.Infof("using configmap loader to build sqlite database")
19+
20+
data := map[string]string{
21+
"customResourceDefinitions": manifest.CustomResourceDefinitions,
22+
"clusterServiceVersions": manifest.ClusterServiceVersions,
23+
"packages": manifest.Packages,
24+
}
25+
26+
sqlLoader, err := sqlite.NewSQLLiteLoader(dbName)
27+
if err != nil {
28+
return
29+
}
30+
31+
configMapPopulator := sqlite.NewSQLLoaderForConfigMapData(l.logger, sqlLoader, data)
32+
if err = configMapPopulator.Populate(); err != nil {
33+
return
34+
}
35+
36+
s, err := sqlite.NewSQLLiteQuerier(dbName)
37+
if err != nil {
38+
err = fmt.Errorf("failed to load db: %v", err)
39+
return
40+
}
41+
42+
// sanity check that the db is available.
43+
tables, err := s.ListTables(context.TODO())
44+
if err != nil {
45+
err = fmt.Errorf("couldn't list tables in db, incorrect config: %v", err)
46+
return
47+
}
48+
49+
if len(tables) == 0 {
50+
err = fmt.Errorf("no tables found in db")
51+
return
52+
}
53+
54+
store = s
55+
return
56+
}

‎pkg/appregistry/downloader.go

+180
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
package appregistry
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/operator-framework/operator-marketplace/pkg/apis/marketplace/v1alpha1"
7+
marketplace "github.com/operator-framework/operator-marketplace/pkg/client/clientset/versioned"
8+
"github.com/operator-framework/operator-registry/pkg/apprclient"
9+
"github.com/sirupsen/logrus"
10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
"k8s.io/apimachinery/pkg/types"
12+
utilerrors "k8s.io/apimachinery/pkg/util/errors"
13+
)
14+
15+
// downloadItem encapsulates the data that is needed to download a specific repository.
16+
type downloadItem struct {
17+
// Repository points to the repository and the particular release that needs
18+
// to be downloaded.
19+
RepositoryMetadata *apprclient.RegistryMetadata
20+
21+
// Spec refers to the remote appregistry URL and remote registry namespace.
22+
Spec *v1alpha1.OperatorSourceSpec
23+
}
24+
25+
func (d *downloadItem) String() string {
26+
return fmt.Sprintf("%s", d.RepositoryMetadata)
27+
}
28+
29+
type downloader struct {
30+
logger *logrus.Entry
31+
client marketplace.Interface
32+
}
33+
34+
// Download downloads manifest(s) associated with the specified package(s) from
35+
// the corresponding operator source(s).
36+
//
37+
// We take a best effort approach in downloading.
38+
//
39+
// If an OperatorSource is not found, we skip it and move on to the next source.
40+
func (d *downloader) Download(input *Input) (manifests []*apprclient.OperatorMetadata, err error) {
41+
items, err := d.Prepare(input)
42+
if err != nil {
43+
d.logger.Errorf("the following error(s) occurred while preparing the download list: %v", err)
44+
45+
if len(items) == 0 {
46+
d.logger.Infof("download list is empty, bailing out: %s", input.Packages)
47+
return
48+
}
49+
}
50+
51+
d.logger.Infof("resolved the following packages: %s", items)
52+
53+
manifests, err = d.DownloadRepositories(items)
54+
55+
return
56+
}
57+
58+
// Prepare prepares the list of repositories to download by resolving each
59+
// package specified to its corresponding operator source.
60+
//
61+
// If a package is specified more than once, the operator source that it
62+
// resolves to the first time is picked.
63+
//
64+
// We apply a best-effort approach here, if a package is can't be resolved we
65+
// log it and move on.
66+
func (d *downloader) Prepare(input *Input) (items []*downloadItem, err error) {
67+
packageMap := input.PackagesToMap()
68+
itemMap := map[string]*downloadItem{}
69+
allErrors := []error{}
70+
71+
for _, source := range input.Sources {
72+
if len(packageMap) == 0 {
73+
// All specified package(s) have already been resolved.
74+
break
75+
}
76+
77+
spec, repositoryList, err := d.QuerySource(source)
78+
if err != nil {
79+
allErrors = append(allErrors, err)
80+
d.logger.Infof("skipping operator source due to error: %s", source)
81+
82+
continue
83+
}
84+
85+
for _, metadata := range repositoryList {
86+
// Repository name has a one to one mapping to operator/package name.
87+
// We use this as the key.
88+
key := metadata.Name
89+
90+
if _, ok := packageMap[key]; ok {
91+
// The package specified has been resolved to this repository
92+
// name in remote registry.
93+
itemMap[key] = &downloadItem{
94+
RepositoryMetadata: metadata,
95+
Spec: spec,
96+
}
97+
98+
// Remove the package specified since it has been resolved.
99+
delete(packageMap, key)
100+
}
101+
}
102+
}
103+
104+
// We might still have packages specified that have not been resolved.
105+
if len(packageMap) > 0 {
106+
d.logger.Infof("the following packages could not be resolved: %v", packageMap)
107+
}
108+
109+
items = make([]*downloadItem, 0)
110+
for _, v := range itemMap {
111+
items = append(items, v)
112+
}
113+
114+
err = utilerrors.NewAggregate(allErrors)
115+
return
116+
}
117+
118+
// DownloadRepositories iterates through each download item and downloads
119+
// operator manifest from the corresponding repository.
120+
func (d *downloader) DownloadRepositories(items []*downloadItem) (manifests []*apprclient.OperatorMetadata, err error) {
121+
allErrors := []error{}
122+
123+
manifests = make([]*apprclient.OperatorMetadata, 0)
124+
for _, item := range items {
125+
endpoint := item.Spec.Endpoint
126+
127+
d.logger.Infof("downloading repository: %s from %s", item.RepositoryMetadata, endpoint)
128+
129+
factory := apprclient.NewClientFactory()
130+
131+
client, err := factory.New("appregistry", endpoint)
132+
if err != nil {
133+
allErrors = append(allErrors, err)
134+
d.logger.Infof("skipping repository: %s", item.RepositoryMetadata)
135+
136+
continue
137+
}
138+
139+
manifest, err := client.RetrieveOne(item.RepositoryMetadata.ID(), item.RepositoryMetadata.Release)
140+
if err != nil {
141+
allErrors = append(allErrors, err)
142+
d.logger.Infof("skipping repository: %s", item.RepositoryMetadata)
143+
144+
continue
145+
}
146+
147+
manifests = append(manifests, manifest)
148+
}
149+
150+
err = utilerrors.NewAggregate(allErrors)
151+
return
152+
}
153+
154+
// QuerySource retrives the OperatorSource object specified by key. It queries
155+
// the registry namespace to list all the repositories associated with this
156+
// operator source.
157+
//
158+
// The function returns the spec ( associated with the OperatorSource object )
159+
// in the cluster and the list of repositories in remote registry associated
160+
// with it.
161+
func (d *downloader) QuerySource(key *types.NamespacedName) (spec *v1alpha1.OperatorSourceSpec, repositories []*apprclient.RegistryMetadata, err error) {
162+
opsrc, err := d.client.MarketplaceV1alpha1().OperatorSources(key.Namespace).Get(key.Name, metav1.GetOptions{})
163+
if err != nil {
164+
return
165+
}
166+
167+
factory := apprclient.NewClientFactory()
168+
client, err := factory.New("appregistry", opsrc.Spec.Endpoint)
169+
if err != nil {
170+
return
171+
}
172+
173+
repositories, err = client.ListPackages(opsrc.Spec.RegistryNamespace)
174+
if err != nil {
175+
return
176+
}
177+
178+
spec = &opsrc.Spec
179+
return
180+
}

‎pkg/appregistry/input.go

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package appregistry
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"strings"
7+
8+
"k8s.io/apimachinery/pkg/types"
9+
)
10+
11+
type Input struct {
12+
// Sources is the set of namespaced name(s) of OperatorSource objects from
13+
// which we need to pull packages.
14+
Sources []*types.NamespacedName
15+
16+
// Packages is the set of package name(s) specified.
17+
Packages []string
18+
}
19+
20+
func (i *Input) PackagesToMap() map[string]bool {
21+
packages := map[string]bool{}
22+
23+
for _, pkg := range i.Packages {
24+
packages[pkg] = false
25+
}
26+
27+
return packages
28+
}
29+
30+
type inputParser struct {
31+
}
32+
33+
// Parse parses the raw input provided, sanitizes it and returns an instance of
34+
// Input.
35+
//
36+
// csvSources is a comma separated list of namespaced name that specifies
37+
// the operator source(s), it is expected to comply to the following format -
38+
// {namespace}/{name},{namespace}/{name},
39+
//
40+
// csvPackages is a comma separated list of packages. It is expected to have
41+
// the following format.
42+
// etcd,prometheus,descheduler
43+
func (p *inputParser) Parse(csvSources string, csvPackages string) (*Input, error) {
44+
sources, err := parseSources(csvSources)
45+
if err != nil {
46+
return nil, err
47+
}
48+
49+
packages := sanitizePackageList(strings.Split(csvPackages, ","))
50+
51+
return &Input{
52+
Sources: sources,
53+
Packages: packages,
54+
}, nil
55+
}
56+
57+
func parseSources(csvSources string) ([]*types.NamespacedName, error) {
58+
values := strings.Split(csvSources, ",")
59+
if len(values) == 0 {
60+
return nil, errors.New(fmt.Sprintf("No OperatorSource(s) has been specified"))
61+
}
62+
63+
names := make([]*types.NamespacedName, 0)
64+
for _, v := range values {
65+
name, err := split(v)
66+
if err != nil {
67+
return nil, err
68+
}
69+
70+
names = append(names, name)
71+
}
72+
73+
return names, nil
74+
}
75+
76+
// sanitizePackageList sanitizes the set of package(s) specified. It removes
77+
// duplicates and ignores empty string.
78+
func sanitizePackageList(in []string) []string {
79+
out := make([]string, 0)
80+
81+
inMap := map[string]bool{}
82+
for _, item := range in {
83+
if _, ok := inMap[item]; ok || item == "" {
84+
continue
85+
}
86+
87+
out = append(out, item)
88+
}
89+
90+
return out
91+
}
92+
93+
func split(sourceName string) (*types.NamespacedName, error) {
94+
split := strings.Split(sourceName, "/")
95+
if len(split) != 2 {
96+
return nil, errors.New(fmt.Sprintf("OperatorSource name should be specified in this format {namespace}/{name}"))
97+
}
98+
99+
return &types.NamespacedName{
100+
Namespace: split[0],
101+
Name: split[1],
102+
}, nil
103+
}

‎pkg/appregistry/merger.go

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package appregistry
2+
3+
import (
4+
"github.com/operator-framework/operator-registry/pkg/apprclient"
5+
"github.com/sirupsen/logrus"
6+
utilerrors "k8s.io/apimachinery/pkg/util/errors"
7+
)
8+
9+
type merger struct {
10+
logger *logrus.Entry
11+
parser ManifestYAMLParser
12+
}
13+
14+
// Merge merges a set of operator manifest(s) into one.
15+
//
16+
// For a given operator source we have N ( N >= 1 ) repositories within the
17+
// given registry namespace. It is required for each repository to contain
18+
// manifest for a single operator.
19+
//
20+
// Once downloaded we can use this function to merge manifest(s) from all
21+
// relevant repositories into an uber manifest.
22+
//
23+
// We assume that all CRD(s), CSV(s) and package(s) are globally unique.
24+
// Otherwise we will fail to load the uber manifest into sqlite.
25+
func (m *merger) Merge(rawManifests []*apprclient.OperatorMetadata) (manifests *RawOperatorManifestData, err error) {
26+
allErrors := []error{}
27+
merged := StructuredOperatorManifestData{}
28+
29+
for _, rawManifest := range rawManifests {
30+
manifest, err := m.parser.Unmarshal(rawManifest.RawYAML)
31+
if err != nil {
32+
allErrors = append(allErrors, err)
33+
m.logger.Infof("skipping repository due to parsing error - %s", rawManifest.RegistryMetadata)
34+
35+
continue
36+
}
37+
38+
merged.Packages = append(merged.Packages, manifest.Packages...)
39+
merged.CustomResourceDefinitions = append(merged.CustomResourceDefinitions, manifest.CustomResourceDefinitions...)
40+
merged.ClusterServiceVersions = append(merged.ClusterServiceVersions, manifest.ClusterServiceVersions...)
41+
}
42+
43+
manifests, err = m.parser.Marshal(&merged)
44+
if err != nil {
45+
allErrors = append(allErrors, err)
46+
}
47+
48+
err = utilerrors.NewAggregate(allErrors)
49+
return
50+
}

‎pkg/appregistry/parser.go

+181
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
package appregistry
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
7+
"github.com/ghodss/yaml"
8+
)
9+
10+
// ManifestYAMLParser is an interface that is responsible for marshaling raw
11+
// operator manifest into structured representation and vice versa.
12+
type ManifestYAMLParser interface {
13+
// Unmarshal unmarshals raw operator manifest YAML into structured
14+
// representation.
15+
//
16+
// The function accepts raw yaml specified in rawYAML and converts it into
17+
// an instance of StructuredOperatorManifestData.
18+
Unmarshal(rawYAML []byte) (marshaled *StructuredOperatorManifestData, err error)
19+
20+
// Marshal marshals a structured representation of an operator manifest into
21+
// raw YAML representation so that it can be used to create a configMap
22+
// object for a catalog source in OLM.
23+
//
24+
// The function accepts a structured representation of operator manifest(s)
25+
// specified in marshaled and returns a raw yaml representation of it.
26+
Marshal(bundle *StructuredOperatorManifestData) (*RawOperatorManifestData, error)
27+
}
28+
29+
// RawOperatorManifestData encapsulates the list of CRD(s), CSV(s) and
30+
// package(s) associated with a set of manifest(s).
31+
type RawOperatorManifestData struct {
32+
// CustomResourceDefinitions is the set of custom resource definition(s)
33+
// associated with this package manifest.
34+
CustomResourceDefinitions string `yaml:"customResourceDefinitions"`
35+
36+
// ClusterServiceVersions is the set of cluster service version(s)
37+
// associated with this package manifest.
38+
ClusterServiceVersions string `yaml:"clusterServiceVersions"`
39+
40+
// Packages is the set of package(s) associated with this operator manifest.
41+
Packages string `yaml:"packages"`
42+
}
43+
44+
// StructuredOperatorManifestData is a structured representation of operator
45+
// manifest(s). An operator manifest is a YAML document with the following
46+
// sections:
47+
// - customResourceDefinitions
48+
// - clusterServiceVersions
49+
// - packages
50+
//
51+
// An operator manifest is unmarshaled into this type so that we can perform
52+
// certain operations like, but not limited to:
53+
// - Construct a new operator manifest object to be used by a CatalogSourceConfig
54+
// by combining a set of existing operator manifest(s).
55+
// - Construct a new operator manifest object by extracting a certain
56+
// operator/package from a a given operator manifest.
57+
type StructuredOperatorManifestData struct {
58+
// CustomResourceDefinitions is the list of custom resource definition(s)
59+
// associated with this operator manifest.
60+
CustomResourceDefinitions []CustomResourceDefinition `json:"customResourceDefinitions"`
61+
62+
// ClusterServiceVersions is the list of cluster service version(s)
63+
//associated with this operators manifest.
64+
ClusterServiceVersions []ClusterServiceVersion `json:"clusterServiceVersions"`
65+
66+
// Packages is the list of package(s) associated with this operator manifest.
67+
Packages []PackageManifest `json:"packages"`
68+
}
69+
70+
// PackageManifest holds information about a package, which is a reference to
71+
// one (or more) channels under a single package.
72+
//
73+
// The following type has been copied as is from OLM.
74+
// See https://github.com/operator-framework/operator-lifecycle-manager/blob/724b209ccfff33b6208cc5d05283800d6661d441/pkg/controller/registry/types.go#L78:6.
75+
//
76+
// We use it to unmarshal 'packages' element of an operator manifest.
77+
type PackageManifest struct {
78+
// PackageName is the name of the overall package, ala `etcd`.
79+
PackageName string `json:"packageName"`
80+
81+
// Channels are the declared channels for the package,
82+
// ala `stable` or `alpha`.
83+
Channels []PackageChannel `json:"channels"`
84+
85+
// DefaultChannelName is, if specified, the name of the default channel for
86+
// the package. The default channel will be installed if no other channel is
87+
// explicitly given. If the package has a single channel, then that
88+
// channel is implicitly the default.
89+
DefaultChannelName string `json:"defaultChannel"`
90+
}
91+
92+
// PackageChannel defines a single channel under a package, pointing to a
93+
// version of that package.
94+
//
95+
// The following type has been directly copied as is from OLM.
96+
// See https://github.com/operator-framework/operator-lifecycle-manager/blob/724b209ccfff33b6208cc5d05283800d6661d441/pkg/controller/registry/types.go#L105.
97+
//
98+
// We use it to unmarshal 'packages/package/channels' element of
99+
// an operator manifest.
100+
type PackageChannel struct {
101+
// Name is the name of the channel, e.g. `alpha` or `stable`.
102+
Name string `json:"name"`
103+
104+
// CurrentCSVName defines a reference to the CSV holding the version of
105+
// this package currently for the channel.
106+
CurrentCSVName string `json:"currentCSV"`
107+
}
108+
109+
type manifestYAMLParser struct{}
110+
111+
func (*manifestYAMLParser) Unmarshal(rawYAML []byte) (*StructuredOperatorManifestData, error) {
112+
var manifestYAML struct {
113+
Data RawOperatorManifestData `yaml:"data"`
114+
}
115+
116+
if err := yaml.Unmarshal(rawYAML, &manifestYAML); err != nil {
117+
return nil, fmt.Errorf("error parsing raw YAML : %s", err)
118+
}
119+
120+
var crds []CustomResourceDefinition
121+
var csvs []ClusterServiceVersion
122+
var packages []PackageManifest
123+
data := manifestYAML.Data
124+
125+
crdJSONRaw, err := yaml.YAMLToJSON([]byte(data.CustomResourceDefinitions))
126+
if err != nil {
127+
return nil, fmt.Errorf("error converting CRD list (YAML) to JSON : %s", err)
128+
}
129+
if err := json.Unmarshal(crdJSONRaw, &crds); err != nil {
130+
return nil, fmt.Errorf("error parsing CRD list (JSON) : %s", err)
131+
}
132+
133+
csvJSONRaw, err := yaml.YAMLToJSON([]byte(data.ClusterServiceVersions))
134+
if err != nil {
135+
return nil, fmt.Errorf("error converting CSV list (YAML) to JSON : %s", err)
136+
}
137+
if err := json.Unmarshal(csvJSONRaw, &csvs); err != nil {
138+
return nil, fmt.Errorf("error parsing CSV list (JSON) : %s", err)
139+
}
140+
141+
packageJSONRaw, err := yaml.YAMLToJSON([]byte(data.Packages))
142+
if err != nil {
143+
return nil, fmt.Errorf("error converting package list (JSON) to YAML : %s", err)
144+
}
145+
if err := json.Unmarshal(packageJSONRaw, &packages); err != nil {
146+
return nil, fmt.Errorf("error parsing package list (JSON) : %s", err)
147+
}
148+
149+
marshaled := &StructuredOperatorManifestData{
150+
CustomResourceDefinitions: crds,
151+
ClusterServiceVersions: csvs,
152+
Packages: packages,
153+
}
154+
155+
return marshaled, nil
156+
}
157+
158+
func (*manifestYAMLParser) Marshal(bundle *StructuredOperatorManifestData) (*RawOperatorManifestData, error) {
159+
crdRaw, err := yaml.Marshal(bundle.CustomResourceDefinitions)
160+
if err != nil {
161+
return nil, fmt.Errorf("error marshaling CRD list into yaml : %s", err)
162+
}
163+
164+
csvRaw, err := yaml.Marshal(bundle.ClusterServiceVersions)
165+
if err != nil {
166+
return nil, fmt.Errorf("error marshaling CSV list into YAML : %s", err)
167+
}
168+
169+
packageRaw, err := yaml.Marshal(bundle.Packages)
170+
if err != nil {
171+
return nil, fmt.Errorf("error marshaling package list into YAML : %s", err)
172+
}
173+
174+
data := &RawOperatorManifestData{
175+
CustomResourceDefinitions: string(crdRaw),
176+
ClusterServiceVersions: string(csvRaw),
177+
Packages: string(packageRaw),
178+
}
179+
180+
return data, nil
181+
}

‎vendor/github.com/operator-framework/operator-marketplace/LICENSE

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

‎vendor/github.com/operator-framework/operator-marketplace/pkg/apis/addtoscheme_marketplace_v1alpha1.go

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

‎vendor/github.com/operator-framework/operator-marketplace/pkg/apis/apis.go

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

‎vendor/github.com/operator-framework/operator-marketplace/pkg/apis/marketplace/v1alpha1/catalogsourceconfig_types.go

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

‎vendor/github.com/operator-framework/operator-marketplace/pkg/apis/marketplace/v1alpha1/doc.go

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

‎vendor/github.com/operator-framework/operator-marketplace/pkg/apis/marketplace/v1alpha1/operatorsource_types.go

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

‎vendor/github.com/operator-framework/operator-marketplace/pkg/apis/marketplace/v1alpha1/phase.go

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

‎vendor/github.com/operator-framework/operator-marketplace/pkg/apis/marketplace/v1alpha1/phase_types.go

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

‎vendor/github.com/operator-framework/operator-marketplace/pkg/apis/marketplace/v1alpha1/register.go

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

‎vendor/github.com/operator-framework/operator-marketplace/pkg/apis/marketplace/v1alpha1/shared.go

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

‎vendor/github.com/operator-framework/operator-marketplace/pkg/apis/marketplace/v1alpha1/zz_generated.deepcopy.go

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

‎vendor/github.com/operator-framework/operator-marketplace/pkg/client/clientset/versioned/clientset.go

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

‎vendor/github.com/operator-framework/operator-marketplace/pkg/client/clientset/versioned/doc.go

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

‎vendor/github.com/operator-framework/operator-marketplace/pkg/client/clientset/versioned/scheme/doc.go

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

‎vendor/github.com/operator-framework/operator-marketplace/pkg/client/clientset/versioned/scheme/register.go

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

‎vendor/github.com/operator-framework/operator-marketplace/pkg/client/clientset/versioned/typed/marketplace/v1alpha1/catalogsourceconfig.go

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

‎vendor/github.com/operator-framework/operator-marketplace/pkg/client/clientset/versioned/typed/marketplace/v1alpha1/doc.go

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

‎vendor/github.com/operator-framework/operator-marketplace/pkg/client/clientset/versioned/typed/marketplace/v1alpha1/generated_expansion.go

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

‎vendor/github.com/operator-framework/operator-marketplace/pkg/client/clientset/versioned/typed/marketplace/v1alpha1/marketplace_client.go

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

‎vendor/github.com/operator-framework/operator-marketplace/pkg/client/clientset/versioned/typed/marketplace/v1alpha1/operatorsource.go

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

‎vendor/modules.txt

+11-3
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,12 @@ github.com/operator-framework/go-appr/appregistry/info
107107
github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1
108108
github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry
109109
github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators
110+
# github.com/operator-framework/operator-marketplace v0.0.0-20190212161948-a7ca81b96ad9
111+
github.com/operator-framework/operator-marketplace/pkg/apis/marketplace/v1alpha1
112+
github.com/operator-framework/operator-marketplace/pkg/client/clientset/versioned
113+
github.com/operator-framework/operator-marketplace/pkg/client/clientset/versioned/typed/marketplace/v1alpha1
114+
github.com/operator-framework/operator-marketplace/pkg/client/clientset/versioned/scheme
115+
github.com/operator-framework/operator-marketplace/pkg/apis
110116
# github.com/peterbourgon/diskv v2.0.1+incompatible
111117
github.com/peterbourgon/diskv
112118
# github.com/pmezard/go-difflib v1.0.0
@@ -213,15 +219,17 @@ k8s.io/api/storage/v1
213219
k8s.io/api/storage/v1alpha1
214220
k8s.io/api/storage/v1beta1
215221
# k8s.io/apiextensions-apiserver v0.0.0-20181204003618-e419c5771cdc
216-
k8s.io/apiextensions-apiserver/pkg/apis/apiextensions
217222
k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1
223+
k8s.io/apiextensions-apiserver/pkg/apis/apiextensions
218224
k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation
219225
k8s.io/apiextensions-apiserver/pkg/apiserver/validation
220226
k8s.io/apiextensions-apiserver/pkg/features
221227
# k8s.io/apimachinery v0.0.0-20190118094746-1525e4dadd2d
222228
k8s.io/apimachinery/pkg/apis/meta/v1
223229
k8s.io/apimachinery/pkg/apis/meta/v1/unstructured
224230
k8s.io/apimachinery/pkg/util/yaml
231+
k8s.io/apimachinery/pkg/types
232+
k8s.io/apimachinery/pkg/util/errors
225233
k8s.io/apimachinery/pkg/runtime
226234
k8s.io/apimachinery/pkg/runtime/serializer
227235
k8s.io/apimachinery/pkg/api/validation
@@ -232,14 +240,12 @@ k8s.io/apimachinery/pkg/api/resource
232240
k8s.io/apimachinery/pkg/fields
233241
k8s.io/apimachinery/pkg/labels
234242
k8s.io/apimachinery/pkg/selection
235-
k8s.io/apimachinery/pkg/types
236243
k8s.io/apimachinery/pkg/util/intstr
237244
k8s.io/apimachinery/pkg/watch
238245
k8s.io/apimachinery/pkg/api/errors
239246
k8s.io/apimachinery/pkg/runtime/serializer/streaming
240247
k8s.io/apimachinery/pkg/util/net
241248
k8s.io/apimachinery/pkg/util/sets
242-
k8s.io/apimachinery/pkg/util/errors
243249
k8s.io/apimachinery/pkg/util/validation
244250
k8s.io/apimachinery/pkg/util/json
245251
k8s.io/apimachinery/pkg/util/runtime
@@ -318,3 +324,5 @@ k8s.io/client-go/util/connrotation
318324
k8s.io/client-go/tools/clientcmd/api/v1
319325
# k8s.io/kube-openapi v0.0.0-20181031203759-72693cb1fadd
320326
k8s.io/kube-openapi/pkg/util/proto
327+
# sigs.k8s.io/controller-runtime v0.1.10
328+
sigs.k8s.io/controller-runtime/pkg/runtime/scheme

‎vendor/sigs.k8s.io/controller-runtime/LICENSE

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

‎vendor/sigs.k8s.io/controller-runtime/pkg/runtime/scheme/scheme.go

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

0 commit comments

Comments
 (0)
Please sign in to comment.