diff --git a/go.mod b/go.mod index e973f287a..68ce9ed5c 100644 --- a/go.mod +++ b/go.mod @@ -31,13 +31,17 @@ require ( github.com/blang/semver v3.5.1+incompatible github.com/ghodss/yaml v1.0.0 github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 // indirect + github.com/golang/protobuf v1.3.2 github.com/hashicorp/golang-lru v0.5.3 // indirect github.com/operator-framework/operator-lifecycle-manager v0.0.0-20191115003340-16619cd27fa5 - github.com/operator-framework/operator-registry v1.5.3 + github.com/operator-framework/operator-registry v1.5.4 github.com/pkg/errors v0.8.1 github.com/sirupsen/logrus v1.4.2 github.com/spf13/cobra v0.0.5 github.com/stretchr/testify v1.4.0 + golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc + google.golang.org/grpc v1.23.0 + k8s.io/api v0.0.0 k8s.io/apiextensions-apiserver v0.0.0 k8s.io/apimachinery v0.0.0 k8s.io/client-go v8.0.0+incompatible diff --git a/go.sum b/go.sum index 58f06bc8b..b852a3ce8 100644 --- a/go.sum +++ b/go.sum @@ -360,6 +360,7 @@ github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsO github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/maxbrunsfeld/counterfeiter/v6 v6.2.1/go.mod h1:F9YacGpnZbLQMzuPI0rR6op21YvNu/RjL705LJJpM3k= +github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= github.com/mesos/mesos-go v0.0.9/go.mod h1:kPYCMQ9gsOXVAle1OsoY4I1+9kPu8GHkf88aV59fDr4= github.com/mholt/certmagic v0.6.2-0.20190624175158-6a42ef9fe8c2/go.mod h1:g4cOPxcjV0oFq3qwpjSA30LReKD8AoIfwAY9VvG35NY= github.com/miekg/dns v1.1.3/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= @@ -410,8 +411,8 @@ github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJ github.com/operator-framework/operator-lifecycle-manager v0.0.0-20191115003340-16619cd27fa5 h1:rjaihxY50c5C+kbQIK4s36R8zxByATYrgRbua4eiG6o= github.com/operator-framework/operator-lifecycle-manager v0.0.0-20191115003340-16619cd27fa5/go.mod h1:zL34MNy92LPutBH5gQK+gGhtgTUlZZX03I2G12vWHF4= github.com/operator-framework/operator-registry v1.5.1/go.mod h1:agrQlkWOo1q8U1SAaLSS2WQ+Z9vswNT2M2HFib9iuLY= -github.com/operator-framework/operator-registry v1.5.3 h1:az83WDwgB+tHsmVn+tFq72yQBbaUAye8e4+KkDQmzLs= -github.com/operator-framework/operator-registry v1.5.3/go.mod h1:agrQlkWOo1q8U1SAaLSS2WQ+Z9vswNT2M2HFib9iuLY= +github.com/operator-framework/operator-registry v1.5.4 h1:ssUAU7CaacYCB6p+CJj9nxXowpD89VLOAY+Aw1w2o8Q= +github.com/operator-framework/operator-registry v1.5.4/go.mod h1:6T3+8vu3N5fpQikSA1Tp91sLxRordtmmvF6ynvBg06g= github.com/otiai10/copy v1.0.1 h1:gtBjD8aq4nychvRZ2CyJvFWAw0aja+VHazDdruZKGZA= github.com/otiai10/copy v1.0.1/go.mod h1:8bMCJrAqOtN/d9oyh5HR7HhLQMvcGMpGdwRDYsfOCHc= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= diff --git a/pkg/internal/bundle.go b/pkg/internal/bundle.go index 5b01ddb69..3578c6314 100644 --- a/pkg/internal/bundle.go +++ b/pkg/internal/bundle.go @@ -3,6 +3,8 @@ package manifests import ( "encoding/json" + manifests "github.com/operator-framework/api/pkg/registry/manifests" + "github.com/blang/semver" operatorsv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" "github.com/operator-framework/operator-registry/pkg/registry" @@ -10,9 +12,6 @@ import ( "github.com/pkg/errors" ) -// TODO: use internal version of registry.Bundle/registry.PackageManifest so -// operator-registry can use validation library. - // manifestsLoad loads a manifests directory from disk. type manifestsLoad struct { dir string @@ -70,34 +69,34 @@ func (l *manifestsLoad) ClearNonDefaultBundles(packageName string) error { return nil } -// ManifestsStore knows how to query for an operator's package manifest and +// ManifestsStorer knows how to query for an operator's package manifest and // related bundles. -type ManifestsStore interface { - // GetPackageManifest returns the ManifestsStore's registry.PackageManifest. +type ManifestsStorer interface { + // GetPackageManifest returns the ManifestsStorer's registry.PackageManifest. // The returned object is assumed to be valid. - GetPackageManifest() registry.PackageManifest - // GetBundles returns the ManifestsStore's set of registry.Bundle. These - // bundles are unique by CSV version, since only one operator type should - // exist in one manifests dir. + GetPackageManifest() manifests.PackageManifest + // GetBundles returns the ManifestsStorer's set of Bundles. These bundles + // are unique by CSV version, since only one operator type should exist + // in one manifests dir. // The returned objects are assumed to be valid. - GetBundles() []*registry.Bundle - // GetBundleForVersion returns the ManifestsStore's registry.Bundle for a - // given version string. An error should be returned if the passed version + GetBundles() []*manifests.Bundle + // GetBundleForVersion returns the ManifestsStorer's Bundle for a given + // version string. An error should be returned if the passed version // does not exist in the store. // The returned object is assumed to be valid. - GetBundleForVersion(string) (*registry.Bundle, error) + GetBundleForVersion(string) (*manifests.Bundle, error) } -// manifests implements ManifestsStore -type manifests struct { - pkg registry.PackageManifest - bundles map[string]*registry.Bundle +// manifestsStore implements ManifestsStorer +type manifestsStore struct { + pkg manifests.PackageManifest + bundles map[string]*manifests.Bundle } -// ManifestsStoreForDir populates a ManifestsStore from the metadata in dir. +// ManifestsStoreForDir populates a ManifestsStorer from the metadata in dir. // Each bundle and the package manifest are statically validated, and will // return an error if any are not valid. -func ManifestsStoreForDir(dir string) (ManifestsStore, error) { +func ManifestsStoreForDir(dir string) (ManifestsStorer, error) { load := &manifestsLoad{ dir: dir, bundles: map[string]*registry.Bundle{}, @@ -105,28 +104,58 @@ func ManifestsStoreForDir(dir string) (ManifestsStore, error) { if err := load.populate(); err != nil { return nil, err } - return &manifests{ - pkg: load.pkg, - bundles: load.bundles, + // TODO(estroz): remove when operator-registry migrates to api types. + pkg, bundles := convertRegistryToAPITypes(load.pkg, load.bundles) + return &manifestsStore{ + pkg: pkg, + bundles: bundles, }, nil } -func (l manifests) GetPackageManifest() registry.PackageManifest { - return l.pkg +// TODO(estroz): remove when operator-registry migrates to api types. +func convertRegistryToAPITypes(pkgR registry.PackageManifest, bundlesR map[string]*registry.Bundle) (manifests.PackageManifest, map[string]*manifests.Bundle) { + pkgA := manifests.PackageManifest{ + PackageName: pkgR.PackageName, + DefaultChannelName: pkgR.DefaultChannelName, + } + for _, channel := range pkgR.Channels { + pkgA.Channels = append(pkgA.Channels, manifests.PackageChannel{ + Name: channel.Name, + CurrentCSVName: channel.CurrentCSVName, + }) + } + bundlesA := map[string]*manifests.Bundle{} + for key, bundle := range bundlesR { + b := manifests.Bundle{ + Name: bundle.Name, + Package: bundle.Package, + Channel: bundle.Channel, + BundleImage: bundle.BundleImage, + } + for _, obj := range bundle.Objects { + b.Add(obj.DeepCopy()) + } + bundlesA[key] = &b + } + return pkgA, bundlesA +} + +func (s manifestsStore) GetPackageManifest() manifests.PackageManifest { + return s.pkg } -func (l manifests) GetBundles() (bundles []*registry.Bundle) { - for _, bundle := range l.bundles { +func (s manifestsStore) GetBundles() (bundles []*manifests.Bundle) { + for _, bundle := range s.bundles { bundles = append(bundles, bundle) } return bundles } -func (l manifests) GetBundleForVersion(version string) (*registry.Bundle, error) { +func (s manifestsStore) GetBundleForVersion(version string) (*manifests.Bundle, error) { if _, err := semver.Parse(version); err != nil { return nil, errors.Wrapf(err, "error getting bundle for version %q", version) } - bundle, ok := l.bundles[version] + bundle, ok := s.bundles[version] if !ok { return nil, errors.Errorf("bundle for version %q does not exist", version) } diff --git a/pkg/manifests/directory.go b/pkg/manifests/directory.go index e910b55df..fa11ba655 100644 --- a/pkg/manifests/directory.go +++ b/pkg/manifests/directory.go @@ -4,24 +4,23 @@ import ( "fmt" internal "github.com/operator-framework/api/pkg/internal" + manifests "github.com/operator-framework/api/pkg/registry/manifests" "github.com/operator-framework/api/pkg/validation" "github.com/operator-framework/api/pkg/validation/errors" - - "github.com/operator-framework/operator-registry/pkg/registry" ) // GetManifestsDir parses all bundles and a package manifest from dir, which // are returned if found along with any errors or warnings encountered while // parsing/validating found manifests. -func GetManifestsDir(dir string) (registry.PackageManifest, []*registry.Bundle, []errors.ManifestResult) { - manifests, err := internal.ManifestsStoreForDir(dir) +func GetManifestsDir(dir string) (manifests.PackageManifest, []*manifests.Bundle, []errors.ManifestResult) { + store, err := internal.ManifestsStoreForDir(dir) if err != nil { result := errors.ManifestResult{} result.Add(errors.ErrInvalidParse(fmt.Sprintf("parse manifests from %q", dir), err)) - return registry.PackageManifest{}, nil, []errors.ManifestResult{result} + return manifests.PackageManifest{}, nil, []errors.ManifestResult{result} } - pkg := manifests.GetPackageManifest() - bundles := manifests.GetBundles() + pkg := store.GetPackageManifest() + bundles := store.GetBundles() objs := []interface{}{} for _, obj := range bundles { objs = append(objs, obj) diff --git a/pkg/registry/api/grpc_health_v1/health.pb.go b/pkg/registry/api/grpc_health_v1/health.pb.go new file mode 100644 index 000000000..b4ae7462e --- /dev/null +++ b/pkg/registry/api/grpc_health_v1/health.pb.go @@ -0,0 +1,190 @@ +// Code generated by protoc-gen-go. +// source: health.proto +// DO NOT EDIT! + +/* +Package grpc_health_v1 is a generated protocol buffer package. + +It is generated from these files: + health.proto + +It has these top-level messages: + HealthCheckRequest + HealthCheckResponse +*/ +package grpc_health_v1 + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type HealthCheckResponse_ServingStatus int32 + +const ( + HealthCheckResponse_UNKNOWN HealthCheckResponse_ServingStatus = 0 + HealthCheckResponse_SERVING HealthCheckResponse_ServingStatus = 1 + HealthCheckResponse_NOT_SERVING HealthCheckResponse_ServingStatus = 2 +) + +var HealthCheckResponse_ServingStatus_name = map[int32]string{ + 0: "UNKNOWN", + 1: "SERVING", + 2: "NOT_SERVING", +} +var HealthCheckResponse_ServingStatus_value = map[string]int32{ + "UNKNOWN": 0, + "SERVING": 1, + "NOT_SERVING": 2, +} + +func (x HealthCheckResponse_ServingStatus) String() string { + return proto.EnumName(HealthCheckResponse_ServingStatus_name, int32(x)) +} +func (HealthCheckResponse_ServingStatus) EnumDescriptor() ([]byte, []int) { + return fileDescriptor0, []int{1, 0} +} + +type HealthCheckRequest struct { + Service string `protobuf:"bytes,1,opt,name=service" json:"service,omitempty"` +} + +func (m *HealthCheckRequest) Reset() { *m = HealthCheckRequest{} } +func (m *HealthCheckRequest) String() string { return proto.CompactTextString(m) } +func (*HealthCheckRequest) ProtoMessage() {} +func (*HealthCheckRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +func (m *HealthCheckRequest) GetService() string { + if m != nil { + return m.Service + } + return "" +} + +type HealthCheckResponse struct { + Status HealthCheckResponse_ServingStatus `protobuf:"varint,1,opt,name=status,enum=grpc.health.v1.HealthCheckResponse_ServingStatus" json:"status,omitempty"` +} + +func (m *HealthCheckResponse) Reset() { *m = HealthCheckResponse{} } +func (m *HealthCheckResponse) String() string { return proto.CompactTextString(m) } +func (*HealthCheckResponse) ProtoMessage() {} +func (*HealthCheckResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } + +func (m *HealthCheckResponse) GetStatus() HealthCheckResponse_ServingStatus { + if m != nil { + return m.Status + } + return HealthCheckResponse_UNKNOWN +} + +func init() { + proto.RegisterType((*HealthCheckRequest)(nil), "grpc.health.v1.HealthCheckRequest") + proto.RegisterType((*HealthCheckResponse)(nil), "grpc.health.v1.HealthCheckResponse") + proto.RegisterEnum("grpc.health.v1.HealthCheckResponse_ServingStatus", HealthCheckResponse_ServingStatus_name, HealthCheckResponse_ServingStatus_value) +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// Client API for Health service + +type HealthClient interface { + Check(ctx context.Context, in *HealthCheckRequest, opts ...grpc.CallOption) (*HealthCheckResponse, error) +} + +type healthClient struct { + cc *grpc.ClientConn +} + +func NewHealthClient(cc *grpc.ClientConn) HealthClient { + return &healthClient{cc} +} + +func (c *healthClient) Check(ctx context.Context, in *HealthCheckRequest, opts ...grpc.CallOption) (*HealthCheckResponse, error) { + out := new(HealthCheckResponse) + err := grpc.Invoke(ctx, "/grpc.health.v1.Health/Check", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Health service + +type HealthServer interface { + Check(context.Context, *HealthCheckRequest) (*HealthCheckResponse, error) +} + +func RegisterHealthServer(s *grpc.Server, srv HealthServer) { + s.RegisterService(&_Health_serviceDesc, srv) +} + +func _Health_Check_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HealthCheckRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HealthServer).Check(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.health.v1.Health/Check", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HealthServer).Check(ctx, req.(*HealthCheckRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Health_serviceDesc = grpc.ServiceDesc{ + ServiceName: "grpc.health.v1.Health", + HandlerType: (*HealthServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Check", + Handler: _Health_Check_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "health.proto", +} + +func init() { proto.RegisterFile("health.proto", fileDescriptor0) } + +var fileDescriptor0 = []byte{ + // 204 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0xc9, 0x48, 0x4d, 0xcc, + 0x29, 0xc9, 0xd0, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x4b, 0x2f, 0x2a, 0x48, 0xd6, 0x83, + 0x0a, 0x95, 0x19, 0x2a, 0xe9, 0x71, 0x09, 0x79, 0x80, 0x39, 0xce, 0x19, 0xa9, 0xc9, 0xd9, 0x41, + 0xa9, 0x85, 0xa5, 0xa9, 0xc5, 0x25, 0x42, 0x12, 0x5c, 0xec, 0xc5, 0xa9, 0x45, 0x65, 0x99, 0xc9, + 0xa9, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x30, 0xae, 0xd2, 0x1c, 0x46, 0x2e, 0x61, 0x14, + 0x0d, 0xc5, 0x05, 0xf9, 0x79, 0xc5, 0xa9, 0x42, 0x9e, 0x5c, 0x6c, 0xc5, 0x25, 0x89, 0x25, 0xa5, + 0xc5, 0x60, 0x0d, 0x7c, 0x46, 0x86, 0x7a, 0xa8, 0x16, 0xe9, 0x61, 0xd1, 0xa4, 0x17, 0x0c, 0x32, + 0x34, 0x2f, 0x3d, 0x18, 0xac, 0x31, 0x08, 0x6a, 0x80, 0x92, 0x15, 0x17, 0x2f, 0x8a, 0x84, 0x10, + 0x37, 0x17, 0x7b, 0xa8, 0x9f, 0xb7, 0x9f, 0x7f, 0xb8, 0x9f, 0x00, 0x03, 0x88, 0x13, 0xec, 0x1a, + 0x14, 0xe6, 0xe9, 0xe7, 0x2e, 0xc0, 0x28, 0xc4, 0xcf, 0xc5, 0xed, 0xe7, 0x1f, 0x12, 0x0f, 0x13, + 0x60, 0x32, 0x8a, 0xe2, 0x62, 0x83, 0x58, 0x24, 0x14, 0xc0, 0xc5, 0x0a, 0xb6, 0x4c, 0x48, 0x09, + 0xaf, 0x4b, 0xc0, 0xfe, 0x95, 0x52, 0x26, 0xc2, 0xb5, 0x49, 0x6c, 0xe0, 0x10, 0x34, 0x06, 0x04, + 0x00, 0x00, 0xff, 0xff, 0xac, 0x56, 0x2a, 0xcb, 0x51, 0x01, 0x00, 0x00, +} diff --git a/pkg/registry/api/grpc_health_v1/health.proto b/pkg/registry/api/grpc_health_v1/health.proto new file mode 100644 index 000000000..35eaffab6 --- /dev/null +++ b/pkg/registry/api/grpc_health_v1/health.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +package grpc.health.v1; + +message HealthCheckRequest { + string service = 1; +} + +message HealthCheckResponse { + enum ServingStatus { + UNKNOWN = 0; + SERVING = 1; + NOT_SERVING = 2; + } + ServingStatus status = 1; +} + +service Health { + rpc Check(HealthCheckRequest) returns (HealthCheckResponse); +} diff --git a/pkg/registry/api/registry.pb.go b/pkg/registry/api/registry.pb.go new file mode 100644 index 000000000..4430ca958 --- /dev/null +++ b/pkg/registry/api/registry.pb.go @@ -0,0 +1,1049 @@ +// Code generated by protoc-gen-go. +// source: registry.proto +// DO NOT EDIT! + +/* +Package api is a generated protocol buffer package. + +It is generated from these files: + registry.proto + +It has these top-level messages: + Channel + PackageName + Package + GroupVersionKind + Bundle + ChannelEntry + ListPackageRequest + GetPackageRequest + GetBundleRequest + GetBundleInChannelRequest + GetAllReplacementsRequest + GetReplacementRequest + GetAllProvidersRequest + GetLatestProvidersRequest + GetDefaultProviderRequest +*/ +package api + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type Channel struct { + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + CsvName string `protobuf:"bytes,2,opt,name=csvName" json:"csvName,omitempty"` +} + +func (m *Channel) Reset() { *m = Channel{} } +func (m *Channel) String() string { return proto.CompactTextString(m) } +func (*Channel) ProtoMessage() {} +func (*Channel) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +func (m *Channel) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *Channel) GetCsvName() string { + if m != nil { + return m.CsvName + } + return "" +} + +type PackageName struct { + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` +} + +func (m *PackageName) Reset() { *m = PackageName{} } +func (m *PackageName) String() string { return proto.CompactTextString(m) } +func (*PackageName) ProtoMessage() {} +func (*PackageName) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } + +func (m *PackageName) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +type Package struct { + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + Channels []*Channel `protobuf:"bytes,2,rep,name=channels" json:"channels,omitempty"` + DefaultChannelName string `protobuf:"bytes,3,opt,name=defaultChannelName" json:"defaultChannelName,omitempty"` +} + +func (m *Package) Reset() { *m = Package{} } +func (m *Package) String() string { return proto.CompactTextString(m) } +func (*Package) ProtoMessage() {} +func (*Package) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } + +func (m *Package) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *Package) GetChannels() []*Channel { + if m != nil { + return m.Channels + } + return nil +} + +func (m *Package) GetDefaultChannelName() string { + if m != nil { + return m.DefaultChannelName + } + return "" +} + +type GroupVersionKind struct { + Group string `protobuf:"bytes,1,opt,name=group" json:"group,omitempty"` + Version string `protobuf:"bytes,2,opt,name=version" json:"version,omitempty"` + Kind string `protobuf:"bytes,3,opt,name=kind" json:"kind,omitempty"` + Plural string `protobuf:"bytes,4,opt,name=plural" json:"plural,omitempty"` +} + +func (m *GroupVersionKind) Reset() { *m = GroupVersionKind{} } +func (m *GroupVersionKind) String() string { return proto.CompactTextString(m) } +func (*GroupVersionKind) ProtoMessage() {} +func (*GroupVersionKind) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } + +func (m *GroupVersionKind) GetGroup() string { + if m != nil { + return m.Group + } + return "" +} + +func (m *GroupVersionKind) GetVersion() string { + if m != nil { + return m.Version + } + return "" +} + +func (m *GroupVersionKind) GetKind() string { + if m != nil { + return m.Kind + } + return "" +} + +func (m *GroupVersionKind) GetPlural() string { + if m != nil { + return m.Plural + } + return "" +} + +type Bundle struct { + CsvName string `protobuf:"bytes,1,opt,name=csvName" json:"csvName,omitempty"` + PackageName string `protobuf:"bytes,2,opt,name=packageName" json:"packageName,omitempty"` + ChannelName string `protobuf:"bytes,3,opt,name=channelName" json:"channelName,omitempty"` + CsvJson string `protobuf:"bytes,4,opt,name=csvJson" json:"csvJson,omitempty"` + Object []string `protobuf:"bytes,5,rep,name=object" json:"object,omitempty"` + BundlePath string `protobuf:"bytes,6,opt,name=bundlePath" json:"bundlePath,omitempty"` + ProvidedApis []*GroupVersionKind `protobuf:"bytes,7,rep,name=providedApis" json:"providedApis,omitempty"` + RequiredApis []*GroupVersionKind `protobuf:"bytes,8,rep,name=requiredApis" json:"requiredApis,omitempty"` + Version string `protobuf:"bytes,9,opt,name=version" json:"version,omitempty"` + SkipRange string `protobuf:"bytes,10,opt,name=skipRange" json:"skipRange,omitempty"` +} + +func (m *Bundle) Reset() { *m = Bundle{} } +func (m *Bundle) String() string { return proto.CompactTextString(m) } +func (*Bundle) ProtoMessage() {} +func (*Bundle) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } + +func (m *Bundle) GetCsvName() string { + if m != nil { + return m.CsvName + } + return "" +} + +func (m *Bundle) GetPackageName() string { + if m != nil { + return m.PackageName + } + return "" +} + +func (m *Bundle) GetChannelName() string { + if m != nil { + return m.ChannelName + } + return "" +} + +func (m *Bundle) GetCsvJson() string { + if m != nil { + return m.CsvJson + } + return "" +} + +func (m *Bundle) GetObject() []string { + if m != nil { + return m.Object + } + return nil +} + +func (m *Bundle) GetBundlePath() string { + if m != nil { + return m.BundlePath + } + return "" +} + +func (m *Bundle) GetProvidedApis() []*GroupVersionKind { + if m != nil { + return m.ProvidedApis + } + return nil +} + +func (m *Bundle) GetRequiredApis() []*GroupVersionKind { + if m != nil { + return m.RequiredApis + } + return nil +} + +func (m *Bundle) GetVersion() string { + if m != nil { + return m.Version + } + return "" +} + +func (m *Bundle) GetSkipRange() string { + if m != nil { + return m.SkipRange + } + return "" +} + +type ChannelEntry struct { + PackageName string `protobuf:"bytes,1,opt,name=packageName" json:"packageName,omitempty"` + ChannelName string `protobuf:"bytes,2,opt,name=channelName" json:"channelName,omitempty"` + BundleName string `protobuf:"bytes,3,opt,name=bundleName" json:"bundleName,omitempty"` + Replaces string `protobuf:"bytes,4,opt,name=replaces" json:"replaces,omitempty"` +} + +func (m *ChannelEntry) Reset() { *m = ChannelEntry{} } +func (m *ChannelEntry) String() string { return proto.CompactTextString(m) } +func (*ChannelEntry) ProtoMessage() {} +func (*ChannelEntry) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} } + +func (m *ChannelEntry) GetPackageName() string { + if m != nil { + return m.PackageName + } + return "" +} + +func (m *ChannelEntry) GetChannelName() string { + if m != nil { + return m.ChannelName + } + return "" +} + +func (m *ChannelEntry) GetBundleName() string { + if m != nil { + return m.BundleName + } + return "" +} + +func (m *ChannelEntry) GetReplaces() string { + if m != nil { + return m.Replaces + } + return "" +} + +type ListPackageRequest struct { +} + +func (m *ListPackageRequest) Reset() { *m = ListPackageRequest{} } +func (m *ListPackageRequest) String() string { return proto.CompactTextString(m) } +func (*ListPackageRequest) ProtoMessage() {} +func (*ListPackageRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} } + +type GetPackageRequest struct { + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` +} + +func (m *GetPackageRequest) Reset() { *m = GetPackageRequest{} } +func (m *GetPackageRequest) String() string { return proto.CompactTextString(m) } +func (*GetPackageRequest) ProtoMessage() {} +func (*GetPackageRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} } + +func (m *GetPackageRequest) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +type GetBundleRequest struct { + PkgName string `protobuf:"bytes,1,opt,name=pkgName" json:"pkgName,omitempty"` + ChannelName string `protobuf:"bytes,2,opt,name=channelName" json:"channelName,omitempty"` + CsvName string `protobuf:"bytes,3,opt,name=csvName" json:"csvName,omitempty"` +} + +func (m *GetBundleRequest) Reset() { *m = GetBundleRequest{} } +func (m *GetBundleRequest) String() string { return proto.CompactTextString(m) } +func (*GetBundleRequest) ProtoMessage() {} +func (*GetBundleRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{8} } + +func (m *GetBundleRequest) GetPkgName() string { + if m != nil { + return m.PkgName + } + return "" +} + +func (m *GetBundleRequest) GetChannelName() string { + if m != nil { + return m.ChannelName + } + return "" +} + +func (m *GetBundleRequest) GetCsvName() string { + if m != nil { + return m.CsvName + } + return "" +} + +type GetBundleInChannelRequest struct { + PkgName string `protobuf:"bytes,1,opt,name=pkgName" json:"pkgName,omitempty"` + ChannelName string `protobuf:"bytes,2,opt,name=channelName" json:"channelName,omitempty"` +} + +func (m *GetBundleInChannelRequest) Reset() { *m = GetBundleInChannelRequest{} } +func (m *GetBundleInChannelRequest) String() string { return proto.CompactTextString(m) } +func (*GetBundleInChannelRequest) ProtoMessage() {} +func (*GetBundleInChannelRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{9} } + +func (m *GetBundleInChannelRequest) GetPkgName() string { + if m != nil { + return m.PkgName + } + return "" +} + +func (m *GetBundleInChannelRequest) GetChannelName() string { + if m != nil { + return m.ChannelName + } + return "" +} + +type GetAllReplacementsRequest struct { + CsvName string `protobuf:"bytes,1,opt,name=csvName" json:"csvName,omitempty"` +} + +func (m *GetAllReplacementsRequest) Reset() { *m = GetAllReplacementsRequest{} } +func (m *GetAllReplacementsRequest) String() string { return proto.CompactTextString(m) } +func (*GetAllReplacementsRequest) ProtoMessage() {} +func (*GetAllReplacementsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{10} } + +func (m *GetAllReplacementsRequest) GetCsvName() string { + if m != nil { + return m.CsvName + } + return "" +} + +type GetReplacementRequest struct { + CsvName string `protobuf:"bytes,1,opt,name=csvName" json:"csvName,omitempty"` + PkgName string `protobuf:"bytes,2,opt,name=pkgName" json:"pkgName,omitempty"` + ChannelName string `protobuf:"bytes,3,opt,name=channelName" json:"channelName,omitempty"` +} + +func (m *GetReplacementRequest) Reset() { *m = GetReplacementRequest{} } +func (m *GetReplacementRequest) String() string { return proto.CompactTextString(m) } +func (*GetReplacementRequest) ProtoMessage() {} +func (*GetReplacementRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{11} } + +func (m *GetReplacementRequest) GetCsvName() string { + if m != nil { + return m.CsvName + } + return "" +} + +func (m *GetReplacementRequest) GetPkgName() string { + if m != nil { + return m.PkgName + } + return "" +} + +func (m *GetReplacementRequest) GetChannelName() string { + if m != nil { + return m.ChannelName + } + return "" +} + +type GetAllProvidersRequest struct { + Group string `protobuf:"bytes,1,opt,name=group" json:"group,omitempty"` + Version string `protobuf:"bytes,2,opt,name=version" json:"version,omitempty"` + Kind string `protobuf:"bytes,3,opt,name=kind" json:"kind,omitempty"` + Plural string `protobuf:"bytes,4,opt,name=plural" json:"plural,omitempty"` +} + +func (m *GetAllProvidersRequest) Reset() { *m = GetAllProvidersRequest{} } +func (m *GetAllProvidersRequest) String() string { return proto.CompactTextString(m) } +func (*GetAllProvidersRequest) ProtoMessage() {} +func (*GetAllProvidersRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{12} } + +func (m *GetAllProvidersRequest) GetGroup() string { + if m != nil { + return m.Group + } + return "" +} + +func (m *GetAllProvidersRequest) GetVersion() string { + if m != nil { + return m.Version + } + return "" +} + +func (m *GetAllProvidersRequest) GetKind() string { + if m != nil { + return m.Kind + } + return "" +} + +func (m *GetAllProvidersRequest) GetPlural() string { + if m != nil { + return m.Plural + } + return "" +} + +type GetLatestProvidersRequest struct { + Group string `protobuf:"bytes,1,opt,name=group" json:"group,omitempty"` + Version string `protobuf:"bytes,2,opt,name=version" json:"version,omitempty"` + Kind string `protobuf:"bytes,3,opt,name=kind" json:"kind,omitempty"` + Plural string `protobuf:"bytes,4,opt,name=plural" json:"plural,omitempty"` +} + +func (m *GetLatestProvidersRequest) Reset() { *m = GetLatestProvidersRequest{} } +func (m *GetLatestProvidersRequest) String() string { return proto.CompactTextString(m) } +func (*GetLatestProvidersRequest) ProtoMessage() {} +func (*GetLatestProvidersRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{13} } + +func (m *GetLatestProvidersRequest) GetGroup() string { + if m != nil { + return m.Group + } + return "" +} + +func (m *GetLatestProvidersRequest) GetVersion() string { + if m != nil { + return m.Version + } + return "" +} + +func (m *GetLatestProvidersRequest) GetKind() string { + if m != nil { + return m.Kind + } + return "" +} + +func (m *GetLatestProvidersRequest) GetPlural() string { + if m != nil { + return m.Plural + } + return "" +} + +type GetDefaultProviderRequest struct { + Group string `protobuf:"bytes,1,opt,name=group" json:"group,omitempty"` + Version string `protobuf:"bytes,2,opt,name=version" json:"version,omitempty"` + Kind string `protobuf:"bytes,3,opt,name=kind" json:"kind,omitempty"` + Plural string `protobuf:"bytes,4,opt,name=plural" json:"plural,omitempty"` +} + +func (m *GetDefaultProviderRequest) Reset() { *m = GetDefaultProviderRequest{} } +func (m *GetDefaultProviderRequest) String() string { return proto.CompactTextString(m) } +func (*GetDefaultProviderRequest) ProtoMessage() {} +func (*GetDefaultProviderRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{14} } + +func (m *GetDefaultProviderRequest) GetGroup() string { + if m != nil { + return m.Group + } + return "" +} + +func (m *GetDefaultProviderRequest) GetVersion() string { + if m != nil { + return m.Version + } + return "" +} + +func (m *GetDefaultProviderRequest) GetKind() string { + if m != nil { + return m.Kind + } + return "" +} + +func (m *GetDefaultProviderRequest) GetPlural() string { + if m != nil { + return m.Plural + } + return "" +} + +func init() { + proto.RegisterType((*Channel)(nil), "api.Channel") + proto.RegisterType((*PackageName)(nil), "api.PackageName") + proto.RegisterType((*Package)(nil), "api.Package") + proto.RegisterType((*GroupVersionKind)(nil), "api.GroupVersionKind") + proto.RegisterType((*Bundle)(nil), "api.Bundle") + proto.RegisterType((*ChannelEntry)(nil), "api.ChannelEntry") + proto.RegisterType((*ListPackageRequest)(nil), "api.ListPackageRequest") + proto.RegisterType((*GetPackageRequest)(nil), "api.GetPackageRequest") + proto.RegisterType((*GetBundleRequest)(nil), "api.GetBundleRequest") + proto.RegisterType((*GetBundleInChannelRequest)(nil), "api.GetBundleInChannelRequest") + proto.RegisterType((*GetAllReplacementsRequest)(nil), "api.GetAllReplacementsRequest") + proto.RegisterType((*GetReplacementRequest)(nil), "api.GetReplacementRequest") + proto.RegisterType((*GetAllProvidersRequest)(nil), "api.GetAllProvidersRequest") + proto.RegisterType((*GetLatestProvidersRequest)(nil), "api.GetLatestProvidersRequest") + proto.RegisterType((*GetDefaultProviderRequest)(nil), "api.GetDefaultProviderRequest") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// Client API for Registry service + +type RegistryClient interface { + ListPackages(ctx context.Context, in *ListPackageRequest, opts ...grpc.CallOption) (Registry_ListPackagesClient, error) + GetPackage(ctx context.Context, in *GetPackageRequest, opts ...grpc.CallOption) (*Package, error) + GetBundle(ctx context.Context, in *GetBundleRequest, opts ...grpc.CallOption) (*Bundle, error) + GetBundleForChannel(ctx context.Context, in *GetBundleInChannelRequest, opts ...grpc.CallOption) (*Bundle, error) + GetChannelEntriesThatReplace(ctx context.Context, in *GetAllReplacementsRequest, opts ...grpc.CallOption) (Registry_GetChannelEntriesThatReplaceClient, error) + GetBundleThatReplaces(ctx context.Context, in *GetReplacementRequest, opts ...grpc.CallOption) (*Bundle, error) + GetChannelEntriesThatProvide(ctx context.Context, in *GetAllProvidersRequest, opts ...grpc.CallOption) (Registry_GetChannelEntriesThatProvideClient, error) + GetLatestChannelEntriesThatProvide(ctx context.Context, in *GetLatestProvidersRequest, opts ...grpc.CallOption) (Registry_GetLatestChannelEntriesThatProvideClient, error) + GetDefaultBundleThatProvides(ctx context.Context, in *GetDefaultProviderRequest, opts ...grpc.CallOption) (*Bundle, error) +} + +type registryClient struct { + cc *grpc.ClientConn +} + +func NewRegistryClient(cc *grpc.ClientConn) RegistryClient { + return ®istryClient{cc} +} + +func (c *registryClient) ListPackages(ctx context.Context, in *ListPackageRequest, opts ...grpc.CallOption) (Registry_ListPackagesClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Registry_serviceDesc.Streams[0], c.cc, "/api.Registry/ListPackages", opts...) + if err != nil { + return nil, err + } + x := ®istryListPackagesClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type Registry_ListPackagesClient interface { + Recv() (*PackageName, error) + grpc.ClientStream +} + +type registryListPackagesClient struct { + grpc.ClientStream +} + +func (x *registryListPackagesClient) Recv() (*PackageName, error) { + m := new(PackageName) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *registryClient) GetPackage(ctx context.Context, in *GetPackageRequest, opts ...grpc.CallOption) (*Package, error) { + out := new(Package) + err := grpc.Invoke(ctx, "/api.Registry/GetPackage", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *registryClient) GetBundle(ctx context.Context, in *GetBundleRequest, opts ...grpc.CallOption) (*Bundle, error) { + out := new(Bundle) + err := grpc.Invoke(ctx, "/api.Registry/GetBundle", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *registryClient) GetBundleForChannel(ctx context.Context, in *GetBundleInChannelRequest, opts ...grpc.CallOption) (*Bundle, error) { + out := new(Bundle) + err := grpc.Invoke(ctx, "/api.Registry/GetBundleForChannel", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *registryClient) GetChannelEntriesThatReplace(ctx context.Context, in *GetAllReplacementsRequest, opts ...grpc.CallOption) (Registry_GetChannelEntriesThatReplaceClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Registry_serviceDesc.Streams[1], c.cc, "/api.Registry/GetChannelEntriesThatReplace", opts...) + if err != nil { + return nil, err + } + x := ®istryGetChannelEntriesThatReplaceClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type Registry_GetChannelEntriesThatReplaceClient interface { + Recv() (*ChannelEntry, error) + grpc.ClientStream +} + +type registryGetChannelEntriesThatReplaceClient struct { + grpc.ClientStream +} + +func (x *registryGetChannelEntriesThatReplaceClient) Recv() (*ChannelEntry, error) { + m := new(ChannelEntry) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *registryClient) GetBundleThatReplaces(ctx context.Context, in *GetReplacementRequest, opts ...grpc.CallOption) (*Bundle, error) { + out := new(Bundle) + err := grpc.Invoke(ctx, "/api.Registry/GetBundleThatReplaces", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *registryClient) GetChannelEntriesThatProvide(ctx context.Context, in *GetAllProvidersRequest, opts ...grpc.CallOption) (Registry_GetChannelEntriesThatProvideClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Registry_serviceDesc.Streams[2], c.cc, "/api.Registry/GetChannelEntriesThatProvide", opts...) + if err != nil { + return nil, err + } + x := ®istryGetChannelEntriesThatProvideClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type Registry_GetChannelEntriesThatProvideClient interface { + Recv() (*ChannelEntry, error) + grpc.ClientStream +} + +type registryGetChannelEntriesThatProvideClient struct { + grpc.ClientStream +} + +func (x *registryGetChannelEntriesThatProvideClient) Recv() (*ChannelEntry, error) { + m := new(ChannelEntry) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *registryClient) GetLatestChannelEntriesThatProvide(ctx context.Context, in *GetLatestProvidersRequest, opts ...grpc.CallOption) (Registry_GetLatestChannelEntriesThatProvideClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Registry_serviceDesc.Streams[3], c.cc, "/api.Registry/GetLatestChannelEntriesThatProvide", opts...) + if err != nil { + return nil, err + } + x := ®istryGetLatestChannelEntriesThatProvideClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type Registry_GetLatestChannelEntriesThatProvideClient interface { + Recv() (*ChannelEntry, error) + grpc.ClientStream +} + +type registryGetLatestChannelEntriesThatProvideClient struct { + grpc.ClientStream +} + +func (x *registryGetLatestChannelEntriesThatProvideClient) Recv() (*ChannelEntry, error) { + m := new(ChannelEntry) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *registryClient) GetDefaultBundleThatProvides(ctx context.Context, in *GetDefaultProviderRequest, opts ...grpc.CallOption) (*Bundle, error) { + out := new(Bundle) + err := grpc.Invoke(ctx, "/api.Registry/GetDefaultBundleThatProvides", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Registry service + +type RegistryServer interface { + ListPackages(*ListPackageRequest, Registry_ListPackagesServer) error + GetPackage(context.Context, *GetPackageRequest) (*Package, error) + GetBundle(context.Context, *GetBundleRequest) (*Bundle, error) + GetBundleForChannel(context.Context, *GetBundleInChannelRequest) (*Bundle, error) + GetChannelEntriesThatReplace(*GetAllReplacementsRequest, Registry_GetChannelEntriesThatReplaceServer) error + GetBundleThatReplaces(context.Context, *GetReplacementRequest) (*Bundle, error) + GetChannelEntriesThatProvide(*GetAllProvidersRequest, Registry_GetChannelEntriesThatProvideServer) error + GetLatestChannelEntriesThatProvide(*GetLatestProvidersRequest, Registry_GetLatestChannelEntriesThatProvideServer) error + GetDefaultBundleThatProvides(context.Context, *GetDefaultProviderRequest) (*Bundle, error) +} + +func RegisterRegistryServer(s *grpc.Server, srv RegistryServer) { + s.RegisterService(&_Registry_serviceDesc, srv) +} + +func _Registry_ListPackages_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(ListPackageRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(RegistryServer).ListPackages(m, ®istryListPackagesServer{stream}) +} + +type Registry_ListPackagesServer interface { + Send(*PackageName) error + grpc.ServerStream +} + +type registryListPackagesServer struct { + grpc.ServerStream +} + +func (x *registryListPackagesServer) Send(m *PackageName) error { + return x.ServerStream.SendMsg(m) +} + +func _Registry_GetPackage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetPackageRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RegistryServer).GetPackage(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/api.Registry/GetPackage", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RegistryServer).GetPackage(ctx, req.(*GetPackageRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Registry_GetBundle_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetBundleRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RegistryServer).GetBundle(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/api.Registry/GetBundle", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RegistryServer).GetBundle(ctx, req.(*GetBundleRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Registry_GetBundleForChannel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetBundleInChannelRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RegistryServer).GetBundleForChannel(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/api.Registry/GetBundleForChannel", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RegistryServer).GetBundleForChannel(ctx, req.(*GetBundleInChannelRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Registry_GetChannelEntriesThatReplace_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(GetAllReplacementsRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(RegistryServer).GetChannelEntriesThatReplace(m, ®istryGetChannelEntriesThatReplaceServer{stream}) +} + +type Registry_GetChannelEntriesThatReplaceServer interface { + Send(*ChannelEntry) error + grpc.ServerStream +} + +type registryGetChannelEntriesThatReplaceServer struct { + grpc.ServerStream +} + +func (x *registryGetChannelEntriesThatReplaceServer) Send(m *ChannelEntry) error { + return x.ServerStream.SendMsg(m) +} + +func _Registry_GetBundleThatReplaces_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetReplacementRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RegistryServer).GetBundleThatReplaces(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/api.Registry/GetBundleThatReplaces", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RegistryServer).GetBundleThatReplaces(ctx, req.(*GetReplacementRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Registry_GetChannelEntriesThatProvide_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(GetAllProvidersRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(RegistryServer).GetChannelEntriesThatProvide(m, ®istryGetChannelEntriesThatProvideServer{stream}) +} + +type Registry_GetChannelEntriesThatProvideServer interface { + Send(*ChannelEntry) error + grpc.ServerStream +} + +type registryGetChannelEntriesThatProvideServer struct { + grpc.ServerStream +} + +func (x *registryGetChannelEntriesThatProvideServer) Send(m *ChannelEntry) error { + return x.ServerStream.SendMsg(m) +} + +func _Registry_GetLatestChannelEntriesThatProvide_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(GetLatestProvidersRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(RegistryServer).GetLatestChannelEntriesThatProvide(m, ®istryGetLatestChannelEntriesThatProvideServer{stream}) +} + +type Registry_GetLatestChannelEntriesThatProvideServer interface { + Send(*ChannelEntry) error + grpc.ServerStream +} + +type registryGetLatestChannelEntriesThatProvideServer struct { + grpc.ServerStream +} + +func (x *registryGetLatestChannelEntriesThatProvideServer) Send(m *ChannelEntry) error { + return x.ServerStream.SendMsg(m) +} + +func _Registry_GetDefaultBundleThatProvides_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetDefaultProviderRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RegistryServer).GetDefaultBundleThatProvides(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/api.Registry/GetDefaultBundleThatProvides", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RegistryServer).GetDefaultBundleThatProvides(ctx, req.(*GetDefaultProviderRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Registry_serviceDesc = grpc.ServiceDesc{ + ServiceName: "api.Registry", + HandlerType: (*RegistryServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetPackage", + Handler: _Registry_GetPackage_Handler, + }, + { + MethodName: "GetBundle", + Handler: _Registry_GetBundle_Handler, + }, + { + MethodName: "GetBundleForChannel", + Handler: _Registry_GetBundleForChannel_Handler, + }, + { + MethodName: "GetBundleThatReplaces", + Handler: _Registry_GetBundleThatReplaces_Handler, + }, + { + MethodName: "GetDefaultBundleThatProvides", + Handler: _Registry_GetDefaultBundleThatProvides_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "ListPackages", + Handler: _Registry_ListPackages_Handler, + ServerStreams: true, + }, + { + StreamName: "GetChannelEntriesThatReplace", + Handler: _Registry_GetChannelEntriesThatReplace_Handler, + ServerStreams: true, + }, + { + StreamName: "GetChannelEntriesThatProvide", + Handler: _Registry_GetChannelEntriesThatProvide_Handler, + ServerStreams: true, + }, + { + StreamName: "GetLatestChannelEntriesThatProvide", + Handler: _Registry_GetLatestChannelEntriesThatProvide_Handler, + ServerStreams: true, + }, + }, + Metadata: "registry.proto", +} + +func init() { proto.RegisterFile("registry.proto", fileDescriptor0) } + +var fileDescriptor0 = []byte{ + // 701 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xc4, 0x56, 0x5d, 0x6f, 0xd3, 0x3c, + 0x14, 0x5e, 0xdb, 0xad, 0x1f, 0xa7, 0xd5, 0xab, 0xcd, 0xef, 0x36, 0x42, 0x99, 0xa6, 0xe2, 0x1b, + 0x76, 0x55, 0xc1, 0x00, 0x21, 0x2e, 0xb8, 0xd8, 0x18, 0x54, 0xc0, 0x40, 0x53, 0xc4, 0xc7, 0x05, + 0x57, 0x5e, 0x6b, 0x5a, 0xd3, 0xcc, 0xc9, 0x6c, 0x67, 0xd3, 0xfe, 0x04, 0x37, 0xfc, 0x47, 0x7e, + 0x07, 0x8a, 0xed, 0x24, 0x4e, 0x9a, 0x6e, 0x48, 0x08, 0xb8, 0xeb, 0x39, 0x3e, 0x1f, 0xcf, 0x79, + 0x72, 0x1e, 0xbb, 0xf0, 0x9f, 0xa0, 0x53, 0x26, 0x95, 0xb8, 0x1a, 0x46, 0x22, 0x54, 0x21, 0x6a, + 0x90, 0x88, 0xe1, 0x27, 0xd0, 0x7a, 0x3e, 0x23, 0x9c, 0xd3, 0x00, 0x21, 0x58, 0xe5, 0xe4, 0x8c, + 0x7a, 0xb5, 0x41, 0x6d, 0xaf, 0xe3, 0xeb, 0xdf, 0xc8, 0x83, 0xd6, 0x58, 0x5e, 0xbc, 0x4b, 0xdc, + 0x75, 0xed, 0x4e, 0x4d, 0x7c, 0x17, 0xba, 0x27, 0x64, 0x3c, 0x27, 0x53, 0x9a, 0x98, 0x55, 0xc9, + 0xf8, 0x12, 0x5a, 0x36, 0xa4, 0xb2, 0xf6, 0x1e, 0xb4, 0xc7, 0xa6, 0xb5, 0xf4, 0xea, 0x83, 0xc6, + 0x5e, 0x77, 0xbf, 0x37, 0x24, 0x11, 0x1b, 0x5a, 0x3c, 0x7e, 0x76, 0x8a, 0x86, 0x80, 0x26, 0xf4, + 0x0b, 0x89, 0x03, 0x65, 0xcf, 0x34, 0xa0, 0x86, 0xae, 0x55, 0x71, 0x82, 0x39, 0xac, 0x8f, 0x44, + 0x18, 0x47, 0x1f, 0xa9, 0x90, 0x2c, 0xe4, 0x6f, 0x18, 0x9f, 0xa0, 0x4d, 0x58, 0x9b, 0x26, 0x3e, + 0x0b, 0xc1, 0x18, 0xc9, 0x7c, 0x17, 0x26, 0x28, 0x9d, 0xcf, 0x9a, 0x09, 0xe2, 0x39, 0xe3, 0x13, + 0xdb, 0x45, 0xff, 0x46, 0xdb, 0xd0, 0x8c, 0x82, 0x58, 0x90, 0xc0, 0x5b, 0xd5, 0x5e, 0x6b, 0xe1, + 0x1f, 0x75, 0x68, 0x1e, 0xc6, 0x7c, 0x12, 0x14, 0x08, 0xab, 0x15, 0x08, 0x43, 0x03, 0xe8, 0x46, + 0x39, 0x61, 0xb6, 0x9d, 0xeb, 0x4a, 0x22, 0xc6, 0x0b, 0xf3, 0xb9, 0x2e, 0x5b, 0xfd, 0xb5, 0x0c, + 0xb9, 0x45, 0x90, 0x9a, 0x09, 0xb4, 0xf0, 0xf4, 0x2b, 0x1d, 0x2b, 0x6f, 0x6d, 0xd0, 0x48, 0xa0, + 0x19, 0x0b, 0xed, 0x02, 0x9c, 0x6a, 0x64, 0x27, 0x44, 0xcd, 0xbc, 0xa6, 0x4e, 0x72, 0x3c, 0xe8, + 0x29, 0xf4, 0x22, 0x11, 0x5e, 0xb0, 0x09, 0x9d, 0x1c, 0x44, 0x4c, 0x7a, 0x2d, 0xfd, 0x21, 0xb6, + 0xf4, 0x87, 0x28, 0x73, 0xe8, 0x17, 0x42, 0x93, 0x54, 0x41, 0xcf, 0x63, 0x26, 0x6c, 0x6a, 0xfb, + 0xda, 0x54, 0x37, 0xd4, 0xa5, 0xbd, 0x53, 0xa4, 0x7d, 0x07, 0x3a, 0x72, 0xce, 0x22, 0x9f, 0xf0, + 0x29, 0xf5, 0x40, 0x9f, 0xe5, 0x0e, 0xfc, 0xad, 0x06, 0x3d, 0xfb, 0xa1, 0x5f, 0x70, 0x25, 0xae, + 0xca, 0xa4, 0xd6, 0x6e, 0x24, 0xb5, 0xbe, 0x48, 0x6a, 0x46, 0x91, 0xc3, 0xba, 0xe3, 0x41, 0x7d, + 0x68, 0x0b, 0x1a, 0x05, 0x64, 0x4c, 0xa5, 0x65, 0x3d, 0xb3, 0xf1, 0x26, 0xa0, 0x63, 0x26, 0x95, + 0x5d, 0x73, 0x9f, 0x9e, 0xc7, 0x54, 0x2a, 0x7c, 0x0f, 0x36, 0x46, 0xb4, 0xe4, 0xac, 0x54, 0xc8, + 0x0c, 0xd6, 0x47, 0x54, 0x99, 0xd5, 0x49, 0xe3, 0x3c, 0x68, 0x45, 0xf3, 0xa9, 0xbb, 0x41, 0xd6, + 0xfc, 0x85, 0x51, 0x9c, 0xed, 0x6b, 0x14, 0xe5, 0xfa, 0x09, 0x6e, 0x67, 0x9d, 0x5e, 0xf1, 0x54, + 0x62, 0xbf, 0xdf, 0x12, 0x3f, 0xd6, 0x85, 0x0f, 0x82, 0xc0, 0x37, 0x9c, 0x9c, 0x51, 0xae, 0xa4, + 0x53, 0xb8, 0x5a, 0x0d, 0xf8, 0x0c, 0xb6, 0x46, 0x54, 0x39, 0x39, 0x37, 0xa6, 0xb8, 0x28, 0xeb, + 0xd7, 0xa2, 0x5c, 0x14, 0x0e, 0x56, 0xb0, 0x6d, 0x50, 0x9e, 0x98, 0x0d, 0x16, 0x19, 0xc4, 0x3f, + 0x79, 0x2f, 0x5c, 0x6a, 0x6e, 0x8e, 0x89, 0xa2, 0x52, 0xfd, 0x83, 0xc6, 0x47, 0xe6, 0x66, 0x4c, + 0x3b, 0xff, 0x85, 0xc6, 0xfb, 0xdf, 0xd7, 0xa0, 0xed, 0xdb, 0x67, 0x06, 0x3d, 0x83, 0x9e, 0x23, + 0x0e, 0x89, 0x6e, 0xe9, 0xab, 0x61, 0x51, 0x2f, 0xfd, 0x75, 0x7d, 0xe0, 0x3c, 0x27, 0x78, 0xe5, + 0x7e, 0x0d, 0x3d, 0x02, 0xc8, 0x55, 0x84, 0xb6, 0xcd, 0xbd, 0x52, 0x96, 0x55, 0xbf, 0xe7, 0xe6, + 0xe2, 0x15, 0xf4, 0x00, 0x3a, 0xd9, 0xa2, 0xa3, 0xad, 0x34, 0xa9, 0x20, 0xb1, 0x7e, 0x57, 0xbb, + 0x8d, 0x0f, 0xaf, 0xa0, 0x23, 0xf8, 0x3f, 0x0b, 0x79, 0x19, 0x8a, 0xf4, 0x3d, 0xdc, 0x2d, 0x26, + 0x97, 0x55, 0x53, 0xae, 0xf2, 0x01, 0x76, 0x46, 0x54, 0x39, 0xb7, 0x13, 0xa3, 0xf2, 0xfd, 0x8c, + 0xa4, 0x3b, 0x9e, 0x97, 0xab, 0xd6, 0x4a, 0x7f, 0xc3, 0x7d, 0xfc, 0xf4, 0xed, 0xa6, 0x59, 0x38, + 0xd4, 0x42, 0x31, 0x5d, 0x9c, 0x72, 0x12, 0xf5, 0xd3, 0x7a, 0x8b, 0x22, 0x2a, 0x43, 0xf3, 0x97, + 0x40, 0xb3, 0x9b, 0x81, 0xee, 0x38, 0xd0, 0xca, 0x7b, 0xba, 0x0c, 0xd7, 0x67, 0xc0, 0xd9, 0x6e, + 0x2f, 0xaf, 0x9c, 0x0d, 0x5d, 0x2d, 0x82, 0x65, 0xc5, 0xdf, 0x6a, 0xc0, 0x76, 0x7f, 0xf3, 0xd9, + 0x6d, 0xba, 0xcc, 0xcb, 0x56, 0xaf, 0x78, 0x69, 0xfe, 0xd3, 0xa6, 0xfe, 0xc3, 0xf3, 0xf0, 0x67, + 0x00, 0x00, 0x00, 0xff, 0xff, 0xe1, 0x3c, 0x83, 0x36, 0x02, 0x09, 0x00, 0x00, +} diff --git a/pkg/registry/api/registry.proto b/pkg/registry/api/registry.proto new file mode 100644 index 000000000..84cf90e44 --- /dev/null +++ b/pkg/registry/api/registry.proto @@ -0,0 +1,107 @@ +syntax = "proto3"; + +package api; + +service Registry { + rpc ListPackages(ListPackageRequest) returns (stream PackageName) {} + rpc GetPackage(GetPackageRequest) returns (Package) {} + rpc GetBundle(GetBundleRequest) returns (Bundle) {} + rpc GetBundleForChannel(GetBundleInChannelRequest) returns (Bundle) {} + rpc GetChannelEntriesThatReplace(GetAllReplacementsRequest) returns (stream ChannelEntry) {} + rpc GetBundleThatReplaces(GetReplacementRequest) returns (Bundle) {} + rpc GetChannelEntriesThatProvide(GetAllProvidersRequest) returns (stream ChannelEntry) {} + rpc GetLatestChannelEntriesThatProvide(GetLatestProvidersRequest) returns (stream ChannelEntry) {} + rpc GetDefaultBundleThatProvides(GetDefaultProviderRequest) returns (Bundle) {} +} + + +message Channel{ + string name = 1; + string csvName = 2; +} + +message PackageName{ + string name = 1; +} + +message Package{ + string name = 1; + repeated Channel channels = 2; + string defaultChannelName = 3; +} + +message GroupVersionKind{ + string group = 1; + string version = 2; + string kind = 3; + string plural = 4; +} + +message Bundle{ + string csvName = 1; + string packageName = 2; + string channelName = 3; + string csvJson = 4; + repeated string object = 5; + string bundlePath = 6; + repeated GroupVersionKind providedApis = 7; + repeated GroupVersionKind requiredApis = 8; + string version = 9; + string skipRange = 10; +} + +message ChannelEntry{ + string packageName = 1; + string channelName = 2; + string bundleName = 3; + string replaces = 4; +} + +message ListPackageRequest{} + +message GetPackageRequest{ + string name = 1; +} + +message GetBundleRequest{ + string pkgName = 1; + string channelName = 2; + string csvName = 3; +} + +message GetBundleInChannelRequest{ + string pkgName = 1; + string channelName = 2; +} + +message GetAllReplacementsRequest{ + string csvName = 1; +} + +message GetReplacementRequest{ + string csvName = 1; + string pkgName = 2; + string channelName = 3; +} + +message GetAllProvidersRequest{ + string group = 1; + string version = 2; + string kind = 3; + string plural = 4; +} + +message GetLatestProvidersRequest{ + string group = 1; + string version = 2; + string kind = 3; + string plural = 4; +} + +message GetDefaultProviderRequest{ + string group = 1; + string version = 2; + string kind = 3; + string plural = 4; +} + diff --git a/pkg/registry/manifests/bundle.go b/pkg/registry/manifests/bundle.go new file mode 100644 index 000000000..32d981f39 --- /dev/null +++ b/pkg/registry/manifests/bundle.go @@ -0,0 +1,272 @@ +package registry + +import ( + "fmt" + "strings" + + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apimachinery/pkg/util/yaml" +) + +// Scheme is the default instance of runtime.Scheme to which types in the Kubernetes API are already registered. +var Scheme = runtime.NewScheme() + +// Codecs provides access to encoding and decoding for the scheme +var Codecs = serializer.NewCodecFactory(Scheme) + +func DefaultYAMLDecoder() runtime.Decoder { + return Codecs.UniversalDeserializer() +} + +func init() { + if err := v1beta1.AddToScheme(Scheme); err != nil { + panic(err) + } +} + +type Bundle struct { + Name string + Objects []*unstructured.Unstructured + Package string + Channel string + BundleImage string + csv *ClusterServiceVersion + crds []*v1beta1.CustomResourceDefinition + cacheStale bool +} + +func NewBundle(name, pkgName, channelName string, objs ...*unstructured.Unstructured) *Bundle { + bundle := &Bundle{Name: name, Package: pkgName, Channel: channelName, cacheStale: false} + for _, o := range objs { + bundle.Add(o) + } + return bundle +} + +func NewBundleFromStrings(name, pkgName, channelName string, objs []string) (*Bundle, error) { + unstObjs := []*unstructured.Unstructured{} + for _, o := range objs { + dec := yaml.NewYAMLOrJSONDecoder(strings.NewReader(o), 10) + unst := &unstructured.Unstructured{} + if err := dec.Decode(unst); err != nil { + return nil, err + } + unstObjs = append(unstObjs, unst) + } + return NewBundle(name, pkgName, channelName, unstObjs...), nil +} + +func (b *Bundle) Size() int { + return len(b.Objects) +} + +func (b *Bundle) Add(obj *unstructured.Unstructured) { + b.Objects = append(b.Objects, obj) + b.cacheStale = true +} + +func (b *Bundle) ClusterServiceVersion() (*ClusterServiceVersion, error) { + if err := b.cache(); err != nil { + return nil, err + } + return b.csv, nil +} + +func (b *Bundle) Version() (string, error) { + if err := b.cache(); err != nil { + return "", err + } + return b.csv.GetVersion() +} + +func (b *Bundle) SkipRange() (string, error) { + if err := b.cache(); err != nil { + return "", err + } + return b.csv.GetSkipRange(), nil +} + +func (b *Bundle) CustomResourceDefinitions() ([]*v1beta1.CustomResourceDefinition, error) { + if err := b.cache(); err != nil { + return nil, err + } + return b.crds, nil +} + +func (b *Bundle) ProvidedAPIs() (map[APIKey]struct{}, error) { + provided := map[APIKey]struct{}{} + crds, err := b.CustomResourceDefinitions() + if err != nil { + return nil, err + } + for _, crd := range crds { + for _, v := range crd.Spec.Versions { + provided[APIKey{Group: crd.Spec.Group, Version: v.Name, Kind: crd.Spec.Names.Kind, Plural: crd.Spec.Names.Plural}] = struct{}{} + } + if crd.Spec.Version != "" { + provided[APIKey{Group: crd.Spec.Group, Version: crd.Spec.Version, Kind: crd.Spec.Names.Kind, Plural: crd.Spec.Names.Plural}] = struct{}{} + } + } + + csv, err := b.ClusterServiceVersion() + if err != nil { + return nil, err + } + + ownedAPIs, _, err := csv.GetApiServiceDefinitions() + if err != nil { + return nil, err + } + for _, api := range ownedAPIs { + provided[APIKey{Group: api.Group, Version: api.Version, Kind: api.Kind, Plural: api.Name}] = struct{}{} + } + return provided, nil +} + +func (b *Bundle) RequiredAPIs() (map[APIKey]struct{}, error) { + required := map[APIKey]struct{}{} + csv, err := b.ClusterServiceVersion() + if err != nil { + return nil, err + } + + _, requiredCRDs, err := csv.GetCustomResourceDefintions() + if err != nil { + return nil, err + } + for _, api := range requiredCRDs { + parts := strings.SplitN(api.Name, ".", 2) + if len(parts) < 2 { + return nil, fmt.Errorf("couldn't parse plural.group from crd name: %s", api.Name) + } + required[APIKey{parts[1], api.Version, api.Kind, parts[0]}] = struct{}{} + + } + _, requiredAPIs, err := csv.GetApiServiceDefinitions() + if err != nil { + return nil, err + } + for _, api := range requiredAPIs { + required[APIKey{Group: api.Group, Version: api.Version, Kind: api.Kind, Plural: api.Name}] = struct{}{} + } + return required, nil +} + +func (b *Bundle) AllProvidedAPIsInBundle() error { + csv, err := b.ClusterServiceVersion() + if err != nil { + return err + } + bundleAPIs, err := b.ProvidedAPIs() + if err != nil { + return err + } + ownedCRDs, _, err := csv.GetCustomResourceDefintions() + if err != nil { + return err + } + shouldExist := make(map[APIKey]struct{}, len(ownedCRDs)) + for _, crdDef := range ownedCRDs { + parts := strings.SplitN(crdDef.Name, ".", 2) + if len(parts) < 2 { + return fmt.Errorf("couldn't parse plural.group from crd name: %s", crdDef.Name) + } + shouldExist[APIKey{parts[1], crdDef.Version, crdDef.Kind, parts[0]}] = struct{}{} + } + for key := range shouldExist { + if _, ok := bundleAPIs[key]; !ok { + return fmt.Errorf("couldn't find %v in bundle. found: %v", key, bundleAPIs) + } + } + // note: don't need to check bundle for extension apiserver types, which don't require extra bundle entries + return nil +} + +func (b *Bundle) Serialize() (csvName, bundleImage string, csvBytes []byte, bundleBytes []byte, err error) { + csvCount := 0 + for _, obj := range b.Objects { + objBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj) + if err != nil { + return "", "", nil, nil, err + } + bundleBytes = append(bundleBytes, objBytes...) + + if obj.GetObjectKind().GroupVersionKind().Kind == "ClusterServiceVersion" { + csvName = obj.GetName() + csvBytes, err = runtime.Encode(unstructured.UnstructuredJSONScheme, obj) + if err != nil { + return "", "", nil, nil, err + } + csvCount += 1 + if csvCount > 1 { + return "", "", nil, nil, fmt.Errorf("two csvs found in one bundle") + } + } + } + + return csvName, b.BundleImage, csvBytes, bundleBytes, nil +} + +func (b *Bundle) Images() (map[string]struct{}, error) { + csv, err := b.ClusterServiceVersion() + if err != nil { + return nil, err + } + + images, err := csv.GetOperatorImages() + if err != nil { + return nil, err + } + + relatedImages, err := csv.GetRelatedImages() + if err != nil { + return nil, err + } + for img := range relatedImages { + images[img] = struct{}{} + } + + return images, nil +} + +func (b *Bundle) cache() error { + if !b.cacheStale { + return nil + } + for _, o := range b.Objects { + if o.GetObjectKind().GroupVersionKind().Kind == "ClusterServiceVersion" { + csv := &ClusterServiceVersion{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(o.UnstructuredContent(), csv); err != nil { + return err + } + b.csv = csv + break + } + } + + if b.crds == nil { + b.crds = []*v1beta1.CustomResourceDefinition{} + } + for _, o := range b.Objects { + if o.GetObjectKind().GroupVersionKind().Kind == "CustomResourceDefinition" { + crd := &v1beta1.CustomResourceDefinition{} + // Marshal Unstructured and Decode as CustomResourceDefinition. FromUnstructured has issues + // converting JSON numbers to float64 for CRD minimum/maximum validation. + cb, err := o.MarshalJSON() + if err != nil { + return err + } + dec := serializer.NewCodecFactory(Scheme).UniversalDeserializer() + if _, _, err = dec.Decode(cb, nil, crd); err != nil { + return fmt.Errorf("error decoding CRD: %v", err) + } + b.crds = append(b.crds, crd) + } + } + + b.cacheStale = false + return nil +} diff --git a/pkg/registry/manifests/conversion.go b/pkg/registry/manifests/conversion.go new file mode 100644 index 000000000..ec541d083 --- /dev/null +++ b/pkg/registry/manifests/conversion.go @@ -0,0 +1,83 @@ +package registry + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/operator-framework/api/pkg/registry/api" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/util/yaml" +) + +func PackageManifestToAPIPackage(manifest *PackageManifest) *api.Package { + channels := []*api.Channel{} + for _, c := range manifest.Channels { + channels = append(channels, PackageChannelToAPIChannel(&c)) + } + return &api.Package{ + Name: manifest.PackageName, + DefaultChannelName: manifest.DefaultChannelName, + Channels: channels, + } +} + +func PackageChannelToAPIChannel(channel *PackageChannel) *api.Channel { + return &api.Channel{ + Name: channel.Name, + CsvName: channel.CurrentCSVName, + } +} + +func ChannelEntryToAPIChannelEntry(entry *ChannelEntry) *api.ChannelEntry { + return &api.ChannelEntry{ + PackageName: entry.PackageName, + ChannelName: entry.ChannelName, + BundleName: entry.BundleName, + Replaces: entry.Replaces, + } +} + +// Bundle strings are appended json objects, we need to split them apart +// e.g. {"my":"obj"}{"csv":"data"}{"crd":"too"} +func BundleStringToObjectStrings(bundleString string) ([]string, error) { + objs := []string{} + dec := json.NewDecoder(strings.NewReader(bundleString)) + + for dec.More() { + var m json.RawMessage + err := dec.Decode(&m) + if err != nil { + return nil, err + } + objs = append(objs, string(m)) + } + return objs, nil +} + +func BundleStringToAPIBundle(bundleString string) (*api.Bundle, error) { + objs, err := BundleStringToObjectStrings(bundleString) + if err != nil { + return nil, err + } + out := &api.Bundle{ + Object: objs, + } + for _, o := range objs { + dec := yaml.NewYAMLOrJSONDecoder(strings.NewReader(o), 10) + unst := &unstructured.Unstructured{} + if err := dec.Decode(unst); err != nil { + return nil, err + } + if unst.GetKind() == "ClusterServiceVersion" { + out.CsvName = unst.GetName() + out.CsvJson = o + break + } + } + if out.CsvName == "" { + return nil, fmt.Errorf("no csv in bundle") + } + return out, nil +} diff --git a/pkg/registry/manifests/csv.go b/pkg/registry/manifests/csv.go new file mode 100644 index 000000000..f5005cd2f --- /dev/null +++ b/pkg/registry/manifests/csv.go @@ -0,0 +1,278 @@ +package registry + +import ( + "encoding/json" + + v1 "k8s.io/api/apps/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + // Name of the section under which the list of owned and required list of + // CRD(s) is specified inside an operator manifest. + customResourceDefinitions = "customresourcedefinitions" + + // Name of the section under which the list of owned and required list of + // apiservices is specified inside an operator manifest. + apiServiceDefinitions = "apiservicedefinitions" + + // The yaml attribute that points to the name of an older + // ClusterServiceVersion object that the current ClusterServiceVersion + // replaces. + replaces = "replaces" + + // The yaml attribute that points to the names of older + // ClusterServiceVersion objects that the current ClusterServiceVersion + // skips + skips = "skips" + + // The yaml attribute that specifies the version of the ClusterServiceVersion + // expected to be semver and parseable by blang/semver + version = "version" + + // The yaml attribute that specifies the related images of the ClusterServiceVersion + relatedImages = "relatedImages" + + // The yaml attribute that specifies the skipRange of the ClusterServiceVersion + skipRangeAnnotationKey = "olm.skipRange" +) + +// ClusterServiceVersion is a structured representation of cluster service +// version object(s) specified inside the 'clusterServiceVersions' section of +// an operator manifest. +type ClusterServiceVersion struct { + // Type metadata. + metav1.TypeMeta `json:",inline"` + + // Object metadata. + metav1.ObjectMeta `json:"metadata"` + + // Spec is the raw representation of the 'spec' element of + // ClusterServiceVersion object. Since we are + // not interested in the content of spec we are not parsing it. + Spec json.RawMessage `json:"spec"` +} + +// GetReplaces returns the name of the older ClusterServiceVersion object that +// is replaced by this ClusterServiceVersion object. +// +// If not defined, the function returns an empty string. +func (csv *ClusterServiceVersion) GetReplaces() (string, error) { + var objmap map[string]*json.RawMessage + if err := json.Unmarshal(csv.Spec, &objmap); err != nil { + return "", err + } + + rawValue, ok := objmap[replaces] + if !ok || rawValue == nil { + return "", nil + } + + var replaces string + if err := json.Unmarshal(*rawValue, &replaces); err != nil { + return "", err + } + + return replaces, nil +} + +// GetVersion returns the version of the CSV +// +// If not defined, the function returns an empty string. +func (csv *ClusterServiceVersion) GetVersion() (string, error) { + var objmap map[string]*json.RawMessage + if err := json.Unmarshal(csv.Spec, &objmap); err != nil { + return "", err + } + + rawValue, ok := objmap[version] + if !ok || rawValue == nil { + return "", nil + } + + var v string + if err := json.Unmarshal(*rawValue, &v); err != nil { + return "", err + } + + return v, nil +} + +// GetSkipRange returns the skiprange of the CSV +// +// If not defined, the function returns an empty string. +func (csv *ClusterServiceVersion) GetSkipRange() string { + skipRange, ok := csv.Annotations[skipRangeAnnotationKey] + if !ok { + return "" + } + return skipRange +} + +// GetSkips returns the name of the older ClusterServiceVersion objects that +// are skipped by this ClusterServiceVersion object. +// +// If not defined, the function returns an empty string. +func (csv *ClusterServiceVersion) GetSkips() ([]string, error) { + var objmap map[string]*json.RawMessage + if err := json.Unmarshal(csv.Spec, &objmap); err != nil { + return nil, err + } + + rawValue, ok := objmap[skips] + if !ok || rawValue == nil { + return nil, nil + } + + var skips []string + if err := json.Unmarshal(*rawValue, &skips); err != nil { + return nil, err + } + + return skips, nil +} + +// GetCustomResourceDefintions returns a list of owned and required +// CustomResourceDefinition object(s) specified inside the +// 'customresourcedefinitions' section of a ClusterServiceVersion 'spec'. +// +// owned represents the list of CRD(s) managed by this ClusterServiceVersion +// object. +// required represents the list of CRD(s) that this ClusterServiceVersion +// object depends on. +// +// If owned or required is not defined in the spec then an empty list is +// returned respectively. +func (csv *ClusterServiceVersion) GetCustomResourceDefintions() (owned []*DefinitionKey, required []*DefinitionKey, err error) { + var objmap map[string]*json.RawMessage + + if err = json.Unmarshal(csv.Spec, &objmap); err != nil { + return + } + + rawValue, ok := objmap[customResourceDefinitions] + if !ok || rawValue == nil { + return + } + + var definitions struct { + Owned []*DefinitionKey `json:"owned"` + Required []*DefinitionKey `json:"required"` + } + + if err = json.Unmarshal(*rawValue, &definitions); err != nil { + return + } + + owned = definitions.Owned + required = definitions.Required + return +} + +// GetApiServiceDefinitions returns a list of owned and required +// APISerivces specified inside the +// 'apiservicedefinitions' section of a ClusterServiceVersion 'spec'. +// +// owned represents the list of apiservices managed by this ClusterServiceVersion +// object. +// required represents the list of apiservices that this ClusterServiceVersion +// object depends on. +// +// If owned or required is not defined in the spec then an empty list is +// returned respectively. +func (csv *ClusterServiceVersion) GetApiServiceDefinitions() (owned []*DefinitionKey, required []*DefinitionKey, err error) { + var objmap map[string]*json.RawMessage + + if err = json.Unmarshal(csv.Spec, &objmap); err != nil { + return + } + + rawValue, ok := objmap[apiServiceDefinitions] + if !ok || rawValue == nil { + return + } + + var definitions struct { + Owned []*DefinitionKey `json:"owned"` + Required []*DefinitionKey `json:"required"` + } + + if err = json.Unmarshal(*rawValue, &definitions); err != nil { + return + } + + owned = definitions.Owned + required = definitions.Required + return +} + +// GetRelatedImage returns the list of associated images for the operator +func (csv *ClusterServiceVersion) GetRelatedImages() (imageSet map[string]struct{}, err error) { + var objmap map[string]*json.RawMessage + imageSet = make(map[string]struct{}) + + if err = json.Unmarshal(csv.Spec, &objmap); err != nil { + return + } + + rawValue, ok := objmap[relatedImages] + if !ok || rawValue == nil { + return + } + + type relatedImage struct { + Name string `json:"name"` + Ref string `json:"image"` + } + var relatedImages []relatedImage + if err = json.Unmarshal(*rawValue, &relatedImages); err != nil { + return + } + + for _, img := range relatedImages { + imageSet[img.Ref] = struct{}{} + } + + return +} + +// GetOperatorImages returns a list of any images used to run the operator. +// Currently this pulls any images in the pod specs of operator deployments. +func (csv *ClusterServiceVersion) GetOperatorImages() (map[string]struct{}, error) { + type dep struct { + Name string + Spec v1.DeploymentSpec + } + type strategySpec struct { + Deployments []dep + } + type strategy struct { + Name string `json:"strategy"` + Spec strategySpec `json:"spec"` + } + type csvSpec struct { + Install strategy + } + + var spec csvSpec + if err := json.Unmarshal(csv.Spec, &spec); err != nil { + return nil, err + } + + // this is the only install strategy we know about + if spec.Install.Name != "deployment" { + return nil, nil + } + + images := map[string]struct{}{} + for _, d := range spec.Install.Spec.Deployments { + for _, c := range d.Spec.Template.Spec.Containers { + images[c.Image] = struct{}{} + } + for _, c := range d.Spec.Template.Spec.InitContainers { + images[c.Image] = struct{}{} + } + } + + return images, nil +} diff --git a/pkg/registry/manifests/csv_test.go b/pkg/registry/manifests/csv_test.go new file mode 100644 index 000000000..15ac14fd6 --- /dev/null +++ b/pkg/registry/manifests/csv_test.go @@ -0,0 +1,760 @@ +package registry + +import ( + "encoding/json" + "reflect" + "testing" + + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestClusterServiceVersion_GetApiServiceDefinitions(t *testing.T) { + type fields struct { + TypeMeta v1.TypeMeta + ObjectMeta v1.ObjectMeta + Spec json.RawMessage + } + tests := []struct { + name string + fields fields + wantOwned []*DefinitionKey + wantRequired []*DefinitionKey + wantErr bool + }{ + { + name: "v1alpha1 with owned, required", + fields: fields{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{}, + Spec: json.RawMessage(` + { + "apiservicedefinitions": { + "owned": [ + {"group": "g", "version": "v1", "kind": "K", "name": "Ks.g"} + ], + "required": [ + {"group": "g2", "version": "v1", "kind": "K2", "name": "K2s.g"} + ] + } + }`), + }, + wantOwned: []*DefinitionKey{ + { + Group: "g", + Kind: "K", + Version: "v1", + Name: "Ks.g", + }, + }, + wantRequired: []*DefinitionKey{ + { + Group: "g2", + Kind: "K2", + Version: "v1", + Name: "K2s.g", + }, + }, + }, + { + name: "v1alpha1 with owned", + fields: fields{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{}, + Spec: json.RawMessage(` + { + "apiservicedefinitions": { + "owned": [ + {"group": "g", "version": "v1", "kind": "K", "name": "Ks.g"} + ] + } + }`), + }, + wantOwned: []*DefinitionKey{ + { + Group: "g", + Kind: "K", + Version: "v1", + Name: "Ks.g", + }, + }, + }, + { + name: "v1alpha1 with required", + fields: fields{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{}, + Spec: json.RawMessage(` + { + "apiservicedefinitions": { + "required": [ + {"group": "g2", "version": "v1", "kind": "K2", "name": "K2s.g"} + ] + } + }`), + }, + wantRequired: []*DefinitionKey{ + { + Group: "g2", + Kind: "K2", + Version: "v1", + Name: "K2s.g", + }, + }, + }, + { + name: "v1alpha1 missing owned,required", + fields: fields{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{}, + Spec: json.RawMessage(`{"replaces": 5}`), + }, + }, + { + name: "v1alpha1 malformed owned,required", + fields: fields{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{}, + Spec: json.RawMessage(` + { + "apiservicedefinitions": { + splat: [ + {"glarp": "g2", "version": "v1", "kind": "K2", "name": "K2s.g"} + ] + } + }`), + }, + wantErr: true, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + csv := &ClusterServiceVersion{ + TypeMeta: tt.fields.TypeMeta, + ObjectMeta: tt.fields.ObjectMeta, + Spec: tt.fields.Spec, + } + gotOwned, gotRequired, err := csv.GetApiServiceDefinitions() + if (err != nil) != tt.wantErr { + t.Errorf("GetApiServiceDefinitions() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotOwned, tt.wantOwned) { + t.Errorf("GetApiServiceDefinitions() gotOwned = %v, want %v", gotOwned, tt.wantOwned) + } + if !reflect.DeepEqual(gotRequired, tt.wantRequired) { + t.Errorf("GetApiServiceDefinitions() gotRequired = %v, want %v", gotRequired, tt.wantRequired) + } + }) + } +} + +func TestClusterServiceVersion_GetCustomResourceDefintions(t *testing.T) { + type fields struct { + TypeMeta v1.TypeMeta + ObjectMeta v1.ObjectMeta + Spec json.RawMessage + } + tests := []struct { + name string + fields fields + wantOwned []*DefinitionKey + wantRequired []*DefinitionKey + wantErr bool + }{ + { + name: "v1alpha1 with owned, required", + fields: fields{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{}, + Spec: json.RawMessage(` + { + "customresourcedefinitions": { + "owned": [ + {"group": "g", "version": "v1", "kind": "K", "name": "Ks.g"} + ], + "required": [ + {"group": "g2", "version": "v1", "kind": "K2", "name": "K2s.g"} + ] + } + }`), + }, + wantOwned: []*DefinitionKey{ + { + Group: "g", + Kind: "K", + Version: "v1", + Name: "Ks.g", + }, + }, + wantRequired: []*DefinitionKey{ + { + Group: "g2", + Kind: "K2", + Version: "v1", + Name: "K2s.g", + }, + }, + }, + { + name: "v1alpha1 with owned", + fields: fields{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{}, + Spec: json.RawMessage(` + { + "customresourcedefinitions": { + "owned": [ + {"group": "g", "version": "v1", "kind": "K", "name": "Ks.g"} + ] + } + }`), + }, + wantOwned: []*DefinitionKey{ + { + Group: "g", + Kind: "K", + Version: "v1", + Name: "Ks.g", + }, + }, + }, + { + name: "v1alpha1 with required", + fields: fields{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{}, + Spec: json.RawMessage(` + { + "customresourcedefinitions": { + "required": [ + {"group": "g2", "version": "v1", "kind": "K2", "name": "K2s.g"} + ] + } + }`), + }, + wantRequired: []*DefinitionKey{ + { + Group: "g2", + Kind: "K2", + Version: "v1", + Name: "K2s.g", + }, + }, + }, + { + name: "v1alpha1 missing owned,required", + fields: fields{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{}, + Spec: json.RawMessage(`{"replaces": 5}`), + }, + }, + { + name: "v1alpha1 malformed owned,required", + fields: fields{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{}, + Spec: json.RawMessage(` + { + "customresourcedefinitions": { + splat: [ + {"glarp": "g2", "version": "v1", "kind": "K2", "name": "K2s.g"} + ] + } + }`), + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + csv := &ClusterServiceVersion{ + TypeMeta: tt.fields.TypeMeta, + ObjectMeta: tt.fields.ObjectMeta, + Spec: tt.fields.Spec, + } + gotOwned, gotRequired, err := csv.GetCustomResourceDefintions() + if (err != nil) != tt.wantErr { + t.Errorf("GetCustomResourceDefintions() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotOwned, tt.wantOwned) { + t.Errorf("GetCustomResourceDefintions() gotOwned = %v, want %v", gotOwned, tt.wantOwned) + } + if !reflect.DeepEqual(gotRequired, tt.wantRequired) { + t.Errorf("GetCustomResourceDefintions() gotRequired = %v, want %v", gotRequired, tt.wantRequired) + } + }) + } +} + +func TestClusterServiceVersion_GetReplaces(t *testing.T) { + type fields struct { + TypeMeta v1.TypeMeta + ObjectMeta v1.ObjectMeta + Spec json.RawMessage + } + tests := []struct { + name string + fields fields + want string + wantErr bool + }{ + { + name: "v1alpha1 with replaces", + fields: fields{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{}, + Spec: json.RawMessage(`{"replaces": "etcd-operator.v0.9.2"}`), + }, + want: "etcd-operator.v0.9.2", + }, + { + name: "v1alpha1 no replaces", + fields: fields{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{}, + Spec: json.RawMessage(`{"other": "field"}`), + }, + want: "", + }, + { + name: "v1alpha1 malformed replaces", + fields: fields{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{}, + Spec: json.RawMessage(`{"replaces": 5}`), + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + csv := &ClusterServiceVersion{ + TypeMeta: tt.fields.TypeMeta, + ObjectMeta: tt.fields.ObjectMeta, + Spec: tt.fields.Spec, + } + got, err := csv.GetReplaces() + if (err != nil) != tt.wantErr { + t.Errorf("GetReplaces() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("GetReplaces() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestClusterServiceVersion_GetSkips(t *testing.T) { + type fields struct { + TypeMeta v1.TypeMeta + ObjectMeta v1.ObjectMeta + Spec json.RawMessage + } + tests := []struct { + name string + fields fields + want []string + wantErr bool + }{ + { + name: "v1alpha1 with skips", + fields: fields{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{}, + Spec: json.RawMessage(`{"skips": ["1.0.5", "1.0.4"]}`), + }, + want: []string{"1.0.5", "1.0.4"}, + }, + { + name: "v1alpha1 no skips", + fields: fields{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{}, + Spec: json.RawMessage(`{"other": "field"}`), + }, + want: nil, + }, + { + name: "v1alpha1 malformed skips", + fields: fields{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{}, + Spec: json.RawMessage(`{"skips": 5}`), + }, + wantErr: true, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + csv := &ClusterServiceVersion{ + TypeMeta: tt.fields.TypeMeta, + ObjectMeta: tt.fields.ObjectMeta, + Spec: tt.fields.Spec, + } + got, err := csv.GetSkips() + if (err != nil) != tt.wantErr { + t.Errorf("GetSkips() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetSkips() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestClusterServiceVersion_GetVersion(t *testing.T) { + type fields struct { + TypeMeta v1.TypeMeta + ObjectMeta v1.ObjectMeta + Spec json.RawMessage + } + tests := []struct { + name string + fields fields + want string + wantErr bool + }{ + { + name: "v1alpha1 with version", + fields: fields{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{}, + Spec: json.RawMessage(`{"version": "1.0.5"}`), + }, + want: "1.0.5", + }, + { + name: "v1alpha1 no version", + fields: fields{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{}, + Spec: json.RawMessage(`{"other": "field"}`), + }, + want: "", + }, + { + name: "v1alpha1 malformed version", + fields: fields{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{}, + Spec: json.RawMessage(`{"version": 5}`), + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + csv := &ClusterServiceVersion{ + TypeMeta: tt.fields.TypeMeta, + ObjectMeta: tt.fields.ObjectMeta, + Spec: tt.fields.Spec, + } + got, err := csv.GetVersion() + if (err != nil) != tt.wantErr { + t.Errorf("GetVersion() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("GetVersion() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestClusterServiceVersion_GetRelatedImages(t *testing.T) { + type fields struct { + TypeMeta v1.TypeMeta + ObjectMeta v1.ObjectMeta + Spec json.RawMessage + } + tests := []struct { + name string + fields fields + want map[string]struct{} + wantErr bool + }{ + { + name: "no related images", + fields: fields{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{}, + Spec: json.RawMessage(`{"no": "field"}`), + }, + want: map[string]struct{}{}, + }, + { + name: "one related image", + fields: fields{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{}, + Spec: json.RawMessage(`{"relatedImages": [ + {"name": "test", "image": "quay.io/etcd/etcd-operator@sha256:123"} + ]}`), + }, + want: map[string]struct{}{"quay.io/etcd/etcd-operator@sha256:123": {}}, + }, + { + name: "multiple related images", + fields: fields{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{}, + Spec: json.RawMessage(`{"relatedImages": [ + {"name": "test", "image": "quay.io/etcd/etcd-operator@sha256:123"}, + {"name": "operand", "image": "quay.io/etcd/etcd@sha256:123"} + ]}`), + }, + want: map[string]struct{}{"quay.io/etcd/etcd-operator@sha256:123": {}, "quay.io/etcd/etcd@sha256:123": {}}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + csv := &ClusterServiceVersion{ + TypeMeta: tt.fields.TypeMeta, + ObjectMeta: tt.fields.ObjectMeta, + Spec: tt.fields.Spec, + } + got, err := csv.GetRelatedImages() + if (err != nil) != tt.wantErr { + t.Errorf("GetRelatedImages() error = %v, wantErr %v", err, tt.wantErr) + return + } + require.Equal(t, tt.want, got) + }) + } +} + +func TestClusterServiceVersion_GetOperatorImages(t *testing.T) { + type fields struct { + TypeMeta v1.TypeMeta + ObjectMeta v1.ObjectMeta + Spec json.RawMessage + } + tests := []struct { + name string + fields fields + want map[string]struct{} + wantErr bool + }{ + { + name: "bad strategy", + fields: fields{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{}, + Spec: json.RawMessage(` + {"install": {"strategy": "nope", "spec": {"deployments":[{"name":"etcd-operator","spec":{"template":{"spec":{"containers":[{ + "command":["etcd-operator"], + "image":"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2", + "name":"etcd-operator" + }]}}}}]}}}`), + }, + }, + { + name: "no images", + fields: fields{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{}, + Spec: json.RawMessage(` + {"install": {"strategy": "deployment","spec": {"deployments":[{"name":"etcd-operator","spec":{"template":{"spec": + "containers":[] + }}}}]}}}`), + }, + want: nil, + wantErr: true, + }, + { + name: "one image", + fields: fields{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{}, + Spec: json.RawMessage(` + {"install": {"strategy": "deployment", "spec": {"deployments":[{ + "name":"etcd-operator", + "spec":{ + "template":{ + "spec":{ + "containers":[ + { + "command":["etcd-operator"], + "image":"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2", + "name":"etcd-operator" + } + ] + } + } + }}]}}}`), + }, + want: map[string]struct{}{"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2": {}}, + }, + { + name: "two container images", + fields: fields{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{}, + Spec: json.RawMessage(` + {"install": {"strategy": "deployment", "spec": {"deployments":[{ + "name":"etcd-operator", + "spec":{ + "template":{ + "spec":{ + "containers":[ + { + "command":["etcd-operator"], + "image":"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2", + "name":"etcd-operator" + }, + { + "command":["etcd-operator-2"], + "image":"quay.io/coreos/etcd-operator-2@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2", + "name":"etcd-operator-2" + } + ] + } + } + }}]}}}`), + }, + want: map[string]struct{}{"quay.io/coreos/etcd-operator-2@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2": {}, "quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2": {}}, + }, + { + name: "init container image", + fields: fields{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{}, + Spec: json.RawMessage(` + { + "install": { + "strategy": "deployment", + "spec": { + "deployments":[ + { + "name":"etcd-operator", + "spec":{ + "template":{ + "spec":{ + "initContainers":[ + { + "command":["etcd-operator"], + "image":"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2", + "name":"etcd-operator" + } + ] + } + } + } + } + ] + } + } + }`), + }, + want: map[string]struct{}{"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2": {}}, + }, + { + name: "two init container images", + fields: fields{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{}, + Spec: json.RawMessage(` + { + "install": { + "strategy": "deployment", + "spec": { + "deployments":[ + { + "name":"etcd-operator", + "spec":{ + "template":{ + "spec":{ + "initContainers":[ + { + "command":["etcd-operator"], + "image":"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2", + "name":"etcd-operator" + }, + { + "command":["etcd-operator2"], + "image":"quay.io/coreos/etcd-operator2@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2", + "name":"etcd-operator2" + } + ] + } + } + } + } + ] + } + } + }`), + }, + want: map[string]struct{}{"quay.io/coreos/etcd-operator2@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2": {}, "quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2": {}}, + }, + { + name: "container and init container", + fields: fields{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{}, + Spec: json.RawMessage(` + { + "install": { + "strategy": "deployment", + "spec": { + "deployments":[ + { + "name":"etcd-operator", + "spec":{ + "template":{ + "spec":{ + "initContainers":[ + { + "command":["init-etcd-operator"], + "image":"quay.io/coreos/init-etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2", + "name":"etcd-operator" + }, + { + "command":["init-etcd-operator2"], + "image":"quay.io/coreos/init-etcd-operator2@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2", + "name":"etcd-operator2" + } + ], + "containers":[ + { + "command":["etcd-operator"], + "image":"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2", + "name":"etcd-operator" + }, + { + "command":["etcd-operator2"], + "image":"quay.io/coreos/etcd-operator2@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2", + "name":"etcd-operator2" + } + ] + } + } + } + } + ] + } + } + }`), + }, + want: map[string]struct{}{"quay.io/coreos/etcd-operator2@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2": {}, "quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2": struct{}{}, "quay.io/coreos/init-etcd-operator2@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2": {}, "quay.io/coreos/init-etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2": {}}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + csv := &ClusterServiceVersion{ + TypeMeta: tt.fields.TypeMeta, + ObjectMeta: tt.fields.ObjectMeta, + Spec: tt.fields.Spec, + } + got, err := csv.GetOperatorImages() + if (err != nil) != tt.wantErr { + t.Errorf("GetOperatorImages() error = %v, wantErr %v", err, tt.wantErr) + return + } + require.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/registry/manifests/decode.go b/pkg/registry/manifests/decode.go new file mode 100644 index 000000000..4d418df76 --- /dev/null +++ b/pkg/registry/manifests/decode.go @@ -0,0 +1,43 @@ +package registry + +import ( + "errors" + "fmt" + "io" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/util/yaml" +) + +// DecodeUnstructured decodes a raw stream into a an +// unstructured.Unstructured instance. +func DecodeUnstructured(reader io.Reader) (obj *unstructured.Unstructured, err error) { + decoder := yaml.NewYAMLOrJSONDecoder(reader, 30) + + t := &unstructured.Unstructured{} + if err = decoder.Decode(t); err != nil { + return + } + + obj = t + return +} + +// DecodePackageManifest decodes a raw stream into a a PackageManifest instance. +// If a package name is empty we consider the object invalid! +func DecodePackageManifest(reader io.Reader) (manifest *PackageManifest, err error) { + decoder := yaml.NewYAMLOrJSONDecoder(reader, 30) + + obj := &PackageManifest{} + if decodeErr := decoder.Decode(obj); decodeErr != nil { + err = fmt.Errorf("could not decode contents into package manifest - %v", decodeErr) + return + } + + if obj.PackageName == "" { + err = errors.New("name of package (packageName) is missing") + return + } + + manifest = obj + return +} diff --git a/pkg/registry/manifests/decode_test.go b/pkg/registry/manifests/decode_test.go new file mode 100644 index 000000000..0d0e08667 --- /dev/null +++ b/pkg/registry/manifests/decode_test.go @@ -0,0 +1,97 @@ +package registry + +import ( + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "io" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "os" + "testing" +) + +func TestDecodeUnstructured(t *testing.T) { + tests := []struct { + name string + file string + assertFunc func(t *testing.T, objGot *unstructured.Unstructured, errGot error) + }{ + { + name: "ValidObjectWithKind", + file: "testdata/valid-unstructured.yaml", + assertFunc: func(t *testing.T, objGot *unstructured.Unstructured, errGot error) { + assert.NoError(t, errGot) + assert.NotNil(t, objGot) + + assert.Equal(t, "FooKind", objGot.GetKind()) + }, + }, + + { + name: "InvalidObjectWithoutKind", + file: "testdata/invalid-unstructured.yaml", + assertFunc: func(t *testing.T, objGot *unstructured.Unstructured, errGot error) { + assert.Error(t, errGot) + assert.Nil(t, objGot) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reader := loadFile(t, tt.file) + + objGot, errGot := DecodeUnstructured(reader) + + if tt.assertFunc != nil { + tt.assertFunc(t, objGot, errGot) + } + }) + } +} + +func TestDecodePackageManifest(t *testing.T) { + tests := []struct { + name string + file string + assertFunc func(t *testing.T, packageManifestGot *PackageManifest, errGot error) + }{ + { + name: "WithValidObject", + file: "testdata/valid-package-manifest.yaml", + assertFunc: func(t *testing.T, packageManifestGot *PackageManifest, errGot error) { + assert.NoError(t, errGot) + assert.NotNil(t, packageManifestGot) + + assert.Equal(t, "foo", packageManifestGot.PackageName) + }, + }, + + { + name: "WithoutPackageName", + file: "testdata/invalid-package-manifest.yaml", + assertFunc: func(t *testing.T, packageManifestGot *PackageManifest, errGot error) { + assert.Error(t, errGot) + assert.Nil(t, packageManifestGot) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reader := loadFile(t, tt.file) + + packageManifestGot, errGot := DecodePackageManifest(reader) + + if tt.assertFunc != nil { + tt.assertFunc(t, packageManifestGot, errGot) + } + }) + } +} + +func loadFile(t *testing.T, path string) io.Reader { + reader, err := os.Open(path) + require.NoError(t, err, "unable to load from file %s", path) + + return reader +} diff --git a/pkg/registry/manifests/testdata/invalid-package-manifest.yaml b/pkg/registry/manifests/testdata/invalid-package-manifest.yaml new file mode 100644 index 000000000..f7b158f4b --- /dev/null +++ b/pkg/registry/manifests/testdata/invalid-package-manifest.yaml @@ -0,0 +1,2 @@ +defaultChannel: alpha +channels: diff --git a/pkg/registry/manifests/testdata/invalid-unstructured.yaml b/pkg/registry/manifests/testdata/invalid-unstructured.yaml new file mode 100644 index 000000000..b5142a4ad --- /dev/null +++ b/pkg/registry/manifests/testdata/invalid-unstructured.yaml @@ -0,0 +1,4 @@ +apiVersion: fake.com/v1alpha1 +metadata: + name: foo + namespace: placeholder diff --git a/pkg/registry/manifests/testdata/valid-package-manifest.yaml b/pkg/registry/manifests/testdata/valid-package-manifest.yaml new file mode 100644 index 000000000..1493ac8f6 --- /dev/null +++ b/pkg/registry/manifests/testdata/valid-package-manifest.yaml @@ -0,0 +1,7 @@ +packageName: foo +defaultChannel: alpha +channels: + - name: alpha + currentCSV: foo + - name: beta + currentCSV: bar diff --git a/pkg/registry/manifests/testdata/valid-unstructured.yaml b/pkg/registry/manifests/testdata/valid-unstructured.yaml new file mode 100644 index 000000000..6015cc0a7 --- /dev/null +++ b/pkg/registry/manifests/testdata/valid-unstructured.yaml @@ -0,0 +1,5 @@ +apiVersion: fake.com/v1alpha1 +kind: FooKind +metadata: + name: foo + namespace: placeholder diff --git a/pkg/registry/manifests/types.go b/pkg/registry/manifests/types.go new file mode 100644 index 000000000..7ca1c3e31 --- /dev/null +++ b/pkg/registry/manifests/types.go @@ -0,0 +1,125 @@ +package registry + +import ( + "fmt" + "strings" +) + +// APIKey stores GroupVersionKind for use as map keys +type APIKey struct { + Group string + Version string + Kind string + Plural string +} + +func (k APIKey) String() string { + return fmt.Sprintf("%s/%s/%s (%s)", k.Group, k.Version, k.Kind, k.Plural) +} + +// DefinitionKey represents the metadata for either an APIservice or a CRD from a CSV spec +type DefinitionKey struct { + Group string `json:"group"` + Kind string `json:"kind"` + Name string `json:"name"` + Version string `json:"version"` +} + +// PackageManifest holds information about a package, which is a reference to one (or more) +// channels under a single package. +type PackageManifest struct { + // PackageName is the name of the overall package, ala `etcd`. + PackageName string `json:"packageName" yaml:"packageName"` + + // Channels are the declared channels for the package, ala `stable` or `alpha`. + Channels []PackageChannel `json:"channels" yaml:"channels"` + + // DefaultChannelName is, if specified, the name of the default channel for the package. The + // default channel will be installed if no other channel is explicitly given. If the package + // has a single channel, then that channel is implicitly the default. + DefaultChannelName string `json:"defaultChannel" yaml:"defaultChannel"` +} + +// GetDefaultChannel gets the default channel or returns the only one if there's only one. returns empty string if it +// can't determine the default +func (m PackageManifest) GetDefaultChannel() string { + if m.DefaultChannelName != "" { + return m.DefaultChannelName + } + if len(m.Channels) == 1 { + return m.Channels[0].Name + } + return "" +} + +// PackageChannel defines a single channel under a package, pointing to a version of that +// package. +type PackageChannel struct { + // Name is the name of the channel, e.g. `alpha` or `stable` + Name string `json:"name" yaml:"name"` + + // CurrentCSVName defines a reference to the CSV holding the version of this package currently + // for the channel. + CurrentCSVName string `json:"currentCSV" yaml:"currentCSV"` +} + +// IsDefaultChannel returns true if the PackageChennel is the default for the PackageManifest +func (pc PackageChannel) IsDefaultChannel(pm PackageManifest) bool { + return pc.Name == pm.DefaultChannelName || len(pm.Channels) == 1 +} + +// ChannelEntry is a denormalized node in a channel graph +type ChannelEntry struct { + PackageName string + ChannelName string + BundleName string + Replaces string +} + +// AnnotationsFile holds annotation information about a bundle +type AnnotationsFile struct { + // annotations is a list of annotations for a given bundle + Annotations Annotations `json:"annotations" yaml:"annotations"` +} + +// Annotations is a list of annotations for a given bundle +type Annotations struct { + // PackageName is the name of the overall package, ala `etcd`. + PackageName string `json:"operators.operatorframework.io.bundle.package.v1" yaml:"operators.operatorframework.io.bundle.package.v1"` + + // Channels are a comma separated list of the declared channels for the bundle, ala `stable` or `alpha`. + Channels string `json:"operators.operatorframework.io.bundle.channels.v1" yaml:"operators.operatorframework.io.bundle.channels.v1"` + + // DefaultChannelName is, if specified, the name of the default channel for the package. The + // default channel will be installed if no other channel is explicitly given. If the package + // has a single channel, then that channel is implicitly the default. + DefaultChannelName string `json:"operators.operatorframework.io.bundle.channel.default.v1" yaml:"operators.operatorframework.io.bundle.channel.default.v1"` +} + +// GetName returns the package name of the bundle +func (a *AnnotationsFile) GetName() string { + if a.Annotations.PackageName != "" { + return a.Annotations.PackageName + } + return "" +} + +// GetChannels returns the channels that this bundle should be added to +func (a *AnnotationsFile) GetChannels() []string { + if a.Annotations.Channels != "" { + return strings.Split(a.Annotations.Channels, ",") + } + return []string{} +} + +// GetDefaultChannelName returns the name of the default channel +func (a *AnnotationsFile) GetDefaultChannelName() string { + if a.Annotations.DefaultChannelName != "" { + return a.Annotations.DefaultChannelName + } + channels := a.GetChannels() + if len(channels) == 1 { + return channels[0] + } + return "" +}