Skip to content

Commit 2272df9

Browse files
committedOct 18, 2018
feat(load/query): support provided APIs that are apiservices
1 parent 5a55806 commit 2272df9

10 files changed

+212
-99
lines changed
 

‎configmap.example.yaml

+10
Original file line numberDiff line numberDiff line change
@@ -6040,6 +6040,16 @@ data:
60406040
valueFrom:
60416041
fieldRef:
60426042
fieldPath: metadata.name
6043+
6044+
apiservicedefinitions:
6045+
owned:
6046+
- group: etcd.database.coreos.com
6047+
version: v1beta2
6048+
kind: FakeEtcdObject
6049+
displayName: Fake Object
6050+
description: Fake object for testing apiservices
6051+
deploymentName: svcat-catalog-apiserver
6052+
60436053
customresourcedefinitions:
60446054
owned:
60456055
- name: etcdclusters.etcd.database.coreos.com

‎pkg/registry/bundle.go

+125-18
Original file line numberDiff line numberDiff line change
@@ -32,38 +32,145 @@ func init() {
3232
}
3333
}
3434

35-
func ProvidedAPIs(objs []*unstructured.Unstructured) (map[APIKey]struct{}, error) {
36-
provided := map[APIKey]struct{}{}
35+
type Bundle struct {
36+
objs []*unstructured.Unstructured
37+
csv *v1alpha1.ClusterServiceVersion
38+
crds []*apiextensions.CustomResourceDefinition
39+
cacheStale bool
40+
}
41+
42+
func NewBundle(objs ...*unstructured.Unstructured) *Bundle {
43+
bundle := &Bundle{cacheStale:false}
3744
for _, o := range objs {
38-
if o.GetObjectKind().GroupVersionKind().Kind == "CustomResourceDefinition" {
39-
crd := &apiextensions.CustomResourceDefinition{}
40-
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(o.UnstructuredContent(), crd); err != nil {
41-
return nil, err
42-
}
43-
for _, v := range crd.Spec.Versions {
44-
provided[APIKey{Group: crd.Spec.Group, Version: v.Name, Kind: crd.Spec.Names.Kind}] = struct{}{}
45-
}
46-
if crd.Spec.Version != "" {
47-
provided[APIKey{Group: crd.Spec.Group, Version: crd.Spec.Version, Kind: crd.Spec.Names.Kind}] = struct{}{}
48-
}
45+
bundle.Add(o)
46+
}
47+
return bundle
48+
}
49+
50+
func (b *Bundle) Size() int {
51+
return len(b.objs)
52+
}
53+
54+
func (b *Bundle) Add(obj *unstructured.Unstructured) {
55+
b.objs = append(b.objs, obj)
56+
b.cacheStale = true
57+
}
58+
59+
func (b *Bundle) ClusterServiceVersion() (*v1alpha1.ClusterServiceVersion, error) {
60+
if err := b.cache(); err!=nil {
61+
return nil, err
62+
}
63+
return b.csv, nil
64+
}
65+
66+
func (b *Bundle) CustomResourceDefinitions() ([]*apiextensions.CustomResourceDefinition, error) {
67+
if err := b.cache(); err!=nil {
68+
return nil, err
69+
}
70+
return b.crds, nil
71+
}
72+
73+
func (b *Bundle) ProvidedAPIs() (map[APIKey]struct{}, error) {
74+
provided := map[APIKey]struct{}{}
75+
crds, err := b.CustomResourceDefinitions()
76+
if err != nil {
77+
return nil, err
78+
}
79+
for _, crd := range crds {
80+
for _, v := range crd.Spec.Versions {
81+
provided[APIKey{Group: crd.Spec.Group, Version: v.Name, Kind: crd.Spec.Names.Kind}] = struct{}{}
4982
}
83+
if crd.Spec.Version != "" {
84+
provided[APIKey{Group: crd.Spec.Group, Version: crd.Spec.Version, Kind: crd.Spec.Names.Kind}] = struct{}{}
85+
}
86+
}
5087

51-
//TODO: APIServiceDefinitions
88+
csv, err := b.ClusterServiceVersion()
89+
if err != nil {
90+
return nil, err
91+
}
92+
for _, api := range csv.Spec.APIServiceDefinitions.Owned {
93+
provided[APIKey{Group: api.Group, Version: api.Version, Kind: api.Kind}] = struct{}{}
5294
}
5395
return provided, nil
5496
}
5597

56-
func AllProvidedAPIsInBundle(csv *v1alpha1.ClusterServiceVersion, bundleAPIs map[APIKey]struct{}) error {
57-
shouldExist := make(map[APIKey]struct{}, len(csv.Spec.CustomResourceDefinitions.Owned)+len(csv.Spec.APIServiceDefinitions.Owned))
98+
func (b *Bundle) AllProvidedAPIsInBundle() error {
99+
csv, err := b.ClusterServiceVersion()
100+
if err != nil {
101+
return err
102+
}
103+
bundleAPIs, err := b.ProvidedAPIs()
104+
if err != nil {
105+
return err
106+
}
107+
shouldExist := make(map[APIKey]struct{}, len(csv.Spec.CustomResourceDefinitions.Owned))
58108
for _, crdDef := range csv.Spec.CustomResourceDefinitions.Owned {
59109
parts := strings.SplitAfterN(crdDef.Name, ".", 2)
60110
shouldExist[APIKey{parts[1], crdDef.Version, crdDef.Kind}] = struct{}{}
61111
}
62-
//TODO: APIServiceDefinitions
63112
for key := range shouldExist {
64113
if _, ok := bundleAPIs[key]; !ok {
65-
return fmt.Errorf("couldn't find %v in bundle", key)
114+
return fmt.Errorf("couldn't find %v in bundle. found: %v", key, bundleAPIs)
115+
}
116+
}
117+
// note: don't need to check bundle for extension apiserver types, which don't require extra bundle entries
118+
return nil
119+
}
120+
121+
func (b *Bundle) Serialize() (csvName string, csvBytes []byte, bundleBytes []byte, err error) {
122+
csvCount := 0
123+
for _, obj := range b.objs {
124+
objBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
125+
if err != nil {
126+
return "", nil, nil, err
127+
}
128+
bundleBytes = append(bundleBytes, objBytes...)
129+
130+
if obj.GetObjectKind().GroupVersionKind().Kind == "ClusterServiceVersion" {
131+
csvName = obj.GetName()
132+
csvBytes, err = runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
133+
if err != nil {
134+
return "", nil, nil, err
135+
}
136+
csvCount += 1
137+
if csvCount > 1 {
138+
return "", nil, nil, fmt.Errorf("two csvs found in one bundle")
139+
}
66140
}
67141
}
142+
143+
return csvName, csvBytes, bundleBytes, nil
144+
}
145+
146+
func (b *Bundle) cache() error {
147+
if !b.cacheStale {
148+
return nil
149+
}
150+
for _, o := range b.objs {
151+
if o.GetObjectKind().GroupVersionKind().Kind == "ClusterServiceVersion" {
152+
csv := &v1alpha1.ClusterServiceVersion{}
153+
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(o.UnstructuredContent(), csv); err != nil {
154+
return err
155+
}
156+
b.csv = csv
157+
break
158+
}
159+
}
160+
161+
if b.crds == nil {
162+
b.crds = []*apiextensions.CustomResourceDefinition{}
163+
}
164+
for _, o := range b.objs {
165+
if o.GetObjectKind().GroupVersionKind().Kind == "CustomResourceDefinition" {
166+
crd := &apiextensions.CustomResourceDefinition{}
167+
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(o.UnstructuredContent(), crd); err != nil {
168+
return err
169+
}
170+
b.crds = append(b.crds, crd)
171+
}
172+
}
173+
174+
b.cacheStale = false
68175
return nil
69176
}

‎pkg/registry/interface.go

+1-3
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,10 @@ package registry
22

33
import (
44
"context"
5-
6-
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
75
)
86

97
type Load interface {
10-
AddOperatorBundle(bundleObjs []*unstructured.Unstructured) error
8+
AddOperatorBundle(bundle *Bundle) error
119
AddPackageChannels(manifest PackageManifest) error
1210
AddProvidedApis() error
1311
}

‎pkg/registry/types.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,6 @@ func (pc PackageChannel) IsDefaultChannel(pm PackageManifest) bool {
5454
type ChannelEntry struct {
5555
PackageName string
5656
ChannelName string
57-
BundleName string
58-
Replaces string
57+
BundleName string
58+
Replaces string
5959
}

‎pkg/sqlite/configmap.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func NewSQLLoaderForConfigMap(store registry.Load, configMap v1.ConfigMap) *Conf
3737
return &ConfigMapLoader{
3838
store: store,
3939
configMap: configMap,
40-
crds: map[registry.APIKey]*unstructured.Unstructured{},
40+
crds: map[registry.APIKey]*unstructured.Unstructured{},
4141
}
4242
}
4343

@@ -58,7 +58,7 @@ func (c *ConfigMapLoader) Populate() error {
5858
}
5959

6060
var parsedCRDList []v1beta1.CustomResourceDefinition
61-
if err := json.Unmarshal(crdListJson, &parsedCRDList); err!=nil {
61+
if err := json.Unmarshal(crdListJson, &parsedCRDList); err != nil {
6262
log.WithError(err).Debug("error parsing CRD list")
6363
return err
6464
}
@@ -109,7 +109,7 @@ func (c *ConfigMapLoader) Populate() error {
109109
return err
110110
}
111111

112-
bundle := []*unstructured.Unstructured{{Object: csvUnst}}
112+
bundle := registry.NewBundle(&unstructured.Unstructured{Object: csvUnst})
113113
for _, owned := range csv.Spec.CustomResourceDefinitions.Owned {
114114
split := strings.SplitAfterN(owned.Name, ".", 2)
115115
if len(split) < 2 {
@@ -120,7 +120,7 @@ func (c *ConfigMapLoader) Populate() error {
120120
if crdUnst, ok := c.crds[gvk]; !ok {
121121
log.WithField("gvk", gvk).WithError(err).Debug("couldn't find owned CRD in crd list")
122122
} else {
123-
bundle = append(bundle, crdUnst)
123+
bundle.Add(crdUnst)
124124
}
125125
}
126126

‎pkg/sqlite/configmap_test.go

+7-4
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,12 @@ func TestQuerierForConfigmap(t *testing.T) {
5959
etcdPackage, err := store.GetPackage(context.TODO(), "etcd")
6060
require.NoError(t, err)
6161
require.EqualValues(t, &registry.PackageManifest{
62-
PackageName: "etcd",
63-
DefaultChannelName:"alpha",
62+
PackageName: "etcd",
63+
DefaultChannelName: "alpha",
6464
Channels: []registry.PackageChannel{
6565
{
66-
Name:"alpha",
67-
CurrentCSVName:"etcdoperator.v0.9.2",
66+
Name: "alpha",
67+
CurrentCSVName: "etcdoperator.v0.9.2",
6868
},
6969
},
7070
}, etcdPackage)
@@ -92,6 +92,9 @@ func TestQuerierForConfigmap(t *testing.T) {
9292
{"etcd", "alpha", "etcdoperator.v0.9.0", "etcdoperator.v0.6.1"},
9393
{"etcd", "alpha", "etcdoperator.v0.9.2", "etcdoperator.v0.9.0"}}, etcdChannelEntriesThatProvide)
9494

95+
etcdChannelEntriesThatProvideAPIServer, err := store.GetChannelEntriesThatProvide(context.TODO(), "etcd.database.coreos.com", "v1beta2", "FakeEtcdObject")
96+
require.ElementsMatch(t, []registry.ChannelEntry{{"etcd", "alpha", "etcdoperator.v0.9.0", "etcdoperator.v0.6.1"}}, etcdChannelEntriesThatProvideAPIServer)
97+
9598
etcdLatestChannelEntriesThatProvide, err := store.GetLatestChannelEntriesThatProvide(context.TODO(), "etcdclusters.etcd.database.coreos.com", "v1beta2", "EtcdCluster")
9699
require.NoError(t, err)
97100
require.ElementsMatch(t, []registry.ChannelEntry{{"etcd", "alpha", "etcdoperator.v0.9.2", "etcdoperator.v0.9.0"}}, etcdLatestChannelEntriesThatProvide)

‎pkg/sqlite/directory.go

+9-16
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,10 @@ import (
1717
"k8s.io/client-go/kubernetes/scheme"
1818
)
1919

20-
21-
2220
type SQLPopulator interface {
2321
Populate() error
2422
}
2523

26-
2724
// DirectoryLoader loads a directory of resources into the database
2825
// files ending in `.crd.yaml` will be parsed as CRDs
2926
// files ending in `.clusterserviceversion.yaml` will be parsed as CSVs
@@ -55,7 +52,7 @@ func (d *DirectoryLoader) Populate() error {
5552
}
5653

5754
log.Info("extracting provided API information")
58-
if err := d.store.AddProvidedApis(); err!= nil {
55+
if err := d.store.AddProvidedApis(); err != nil {
5956
return err
6057
}
6158
return nil
@@ -100,37 +97,33 @@ func (d *DirectoryLoader) LoadBundleWalkFunc(path string, f os.FileInfo, err err
10097
return fmt.Errorf("could not decode contents of file %s into CSV: %v", path, err)
10198
}
10299

103-
bundleObjs, err := d.LoadBundle(filepath.Dir(path))
100+
bundle, err := d.LoadBundle(filepath.Dir(path))
104101
if err != nil {
105102
return fmt.Errorf("error loading objs in dir: %s", err.Error())
106103
}
107104

108-
if len(bundleObjs) == 0 {
105+
if bundle.Size() == 0 {
109106
log.Warnf("no bundle objects found")
110107
return nil
111108
}
112109

113-
providedAPIsInBundle, err := registry.ProvidedAPIs(bundleObjs)
114-
if err != nil {
115-
return err
116-
}
117-
if err := registry.AllProvidedAPIsInBundle(&csv, providedAPIsInBundle); err != nil {
110+
if err := bundle.AllProvidedAPIsInBundle(); err != nil {
118111
return err
119112
}
120113

121-
return d.store.AddOperatorBundle(bundleObjs)
114+
return d.store.AddOperatorBundle(bundle)
122115
}
123116

124117
// LoadBundle takes the directory that a CSV is in and assumes the rest of the objects in that directory
125118
// are part of the bundle.
126-
func (d *DirectoryLoader) LoadBundle(dir string) ([]*unstructured.Unstructured, error) {
119+
func (d *DirectoryLoader) LoadBundle(dir string) (*registry.Bundle, error) {
120+
bundle := &registry.Bundle{}
127121
log := logrus.WithFields(logrus.Fields{"dir": d.directory, "load": "bundle"})
128122
files, err := ioutil.ReadDir(dir)
129123
if err != nil {
130124
return nil, err
131125
}
132126

133-
objs := []*unstructured.Unstructured{}
134127
for _, f := range files {
135128
log = log.WithField("file", f.Name())
136129
if f.IsDir() {
@@ -154,11 +147,11 @@ func (d *DirectoryLoader) LoadBundle(dir string) ([]*unstructured.Unstructured,
154147
return nil, fmt.Errorf("could not decode contents of file %s into object: %v", f.Name(), err)
155148
}
156149
if obj != nil {
157-
objs = append(objs, obj)
150+
bundle.Add(obj)
158151
}
159152

160153
}
161-
return objs, nil
154+
return bundle, nil
162155
}
163156

164157
func (d *DirectoryLoader) LoadPackagesWalkFunc(path string, f os.FileInfo, err error) error {

‎pkg/sqlite/directory_test.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,12 @@ func TestQuerierForDirectory(t *testing.T) {
3939
etcdPackage, err := store.GetPackage(context.TODO(), "etcd")
4040
require.NoError(t, err)
4141
require.EqualValues(t, &registry.PackageManifest{
42-
PackageName: "etcd",
43-
DefaultChannelName:"alpha",
42+
PackageName: "etcd",
43+
DefaultChannelName: "alpha",
4444
Channels: []registry.PackageChannel{
4545
{
46-
Name:"alpha",
47-
CurrentCSVName:"etcdoperator.v0.9.2",
46+
Name: "alpha",
47+
CurrentCSVName: "etcdoperator.v0.9.2",
4848
},
4949
},
5050
}, etcdPackage)

‎pkg/sqlite/load.go

+49-46
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,11 @@ package sqlite
33
import (
44
"database/sql"
55
"encoding/json"
6-
"fmt"
76

87
_ "github.com/mattn/go-sqlite3"
98
"github.com/operator-framework/operator-registry/pkg/registry"
10-
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
11-
"k8s.io/apimachinery/pkg/runtime"
129
)
1310

14-
1511
type SQLLoader struct {
1612
db *sql.DB
1713
}
@@ -78,7 +74,7 @@ func NewSQLLiteLoader(outFilename string) (*SQLLoader, error) {
7874
return &SQLLoader{db}, nil
7975
}
8076

81-
func (s *SQLLoader) AddOperatorBundle(bundleObjs []*unstructured.Unstructured) error {
77+
func (s *SQLLoader) AddOperatorBundle(bundle *registry.Bundle) error {
8278
tx, err := s.db.Begin()
8379
if err != nil {
8480
return err
@@ -90,7 +86,7 @@ func (s *SQLLoader) AddOperatorBundle(bundleObjs []*unstructured.Unstructured) e
9086
}
9187
defer stmt.Close()
9288

93-
csvName, csvBytes, bundleBytes, err := s.serializeBundle(bundleObjs)
89+
csvName, csvBytes, bundleBytes, err := bundle.Serialize()
9490
if err != nil {
9591
return err
9692
}
@@ -161,12 +157,12 @@ func (s *SQLLoader) AddPackageChannels(manifest registry.PackageManifest) error
161157
defer addReplaces.Close()
162158

163159
for _, c := range manifest.Channels {
164-
res, err := addChannelEntry.Exec(c.Name, manifest.PackageName, c.CurrentCSVName, 0);
160+
res, err := addChannelEntry.Exec(c.Name, manifest.PackageName, c.CurrentCSVName, 0)
165161
if err != nil {
166162
return err
167163
}
168164
currentID, err := res.LastInsertId()
169-
if err!=nil {
165+
if err != nil {
170166
return err
171167
}
172168

@@ -193,7 +189,7 @@ func (s *SQLLoader) AddPackageChannels(manifest registry.PackageManifest) error
193189
return err
194190
}
195191
replacedID, err := replacedChannelEntry.LastInsertId()
196-
if err!=nil {
192+
if err != nil {
197193
return err
198194
}
199195
addReplaces.Exec(replacedID, currentID)
@@ -212,18 +208,20 @@ func (s *SQLLoader) AddProvidedApis() error {
212208
return err
213209
}
214210
addApi, err := tx.Prepare("insert or replace into api(groupOrName,version,kind) values(?,?,?)")
215-
if err!=nil{
211+
if err != nil {
216212
return err
217213
}
218214
defer addApi.Close()
219215

220216
addApiProvider, err := tx.Prepare("insert into api_provider(groupOrName,version,kind,channel_entry_id) values(?,?,?,?)")
221-
if err!=nil {
217+
if err != nil {
222218
return err
223219
}
224220
defer addApiProvider.Close()
225221

226-
getChannelEntryProvidedAPIs,err := tx.Prepare(`
222+
223+
// get CRD provided APIs
224+
getChannelEntryProvidedAPIs, err := tx.Prepare(`
227225
SELECT DISTINCT channel_entry.entry_id, json_extract(json_each.value, '$.name', '$.version', '$.kind')
228226
FROM channel_entry INNER JOIN operatorbundle,json_each(operatorbundle.csv, '$.spec.customresourcedefinitions.owned')
229227
ON channel_entry.operatorbundle_name = operatorbundle.name`)
@@ -233,58 +231,63 @@ func (s *SQLLoader) AddProvidedApis() error {
233231
defer getChannelEntryProvidedAPIs.Close()
234232

235233
rows, err := getChannelEntryProvidedAPIs.Query()
236-
if err!=nil {
234+
if err != nil {
237235
return err
238236
}
239237
for rows.Next() {
240238
var channelId sql.NullInt64
241239
var gvkSQL sql.NullString
242240

243-
if err := rows.Scan(&channelId, &gvkSQL); err!=nil {
241+
if err := rows.Scan(&channelId, &gvkSQL); err != nil {
244242
return err
245243
}
246244
apigvk := []string{}
247-
if err := json.Unmarshal([]byte(gvkSQL.String), &apigvk); err!= nil {
245+
if err := json.Unmarshal([]byte(gvkSQL.String), &apigvk); err != nil {
246+
return err
247+
}
248+
if _, err := addApi.Exec(apigvk[0], apigvk[1], apigvk[2]); err != nil {
249+
return err
250+
}
251+
if _, err := addApiProvider.Exec(apigvk[0], apigvk[1], apigvk[2], channelId.Int64); err != nil {
248252
return err
249253
}
250-
if _, err := addApi.Exec(apigvk[0], apigvk[1], apigvk[2]); err != nil {
251-
return err
252-
}
253-
if _, err := addApiProvider.Exec(apigvk[0], apigvk[1], apigvk[2], channelId.Int64); err != nil {
254-
return err
255-
}
256254
}
257255

258-
return tx.Commit()
259-
}
256+
getChannelEntryProvidedAPIsAPIservice, err := tx.Prepare(`
257+
SELECT DISTINCT channel_entry.entry_id, json_extract(json_each.value, '$.group', '$.version', '$.kind')
258+
FROM channel_entry INNER JOIN operatorbundle,json_each(operatorbundle.csv, '$.spec.apiservicedefinitions.owned')
259+
ON channel_entry.operatorbundle_name = operatorbundle.name`)
260+
if err != nil {
261+
return err
262+
}
263+
defer getChannelEntryProvidedAPIsAPIservice.Close()
260264

261-
func (s *SQLLoader) Close() {
262-
s.db.Close()
263-
}
265+
rows, err = getChannelEntryProvidedAPIsAPIservice.Query()
266+
if err != nil {
267+
return err
268+
}
269+
for rows.Next() {
270+
var channelId sql.NullInt64
271+
var gvkSQL sql.NullString
264272

265-
func (s *SQLLoader) serializeBundle(bundleObjs []*unstructured.Unstructured) (csvName string, csvBytes []byte, bundleBytes []byte, err error) {
266-
csvCount := 0
267-
for _, obj := range bundleObjs {
268-
objBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
269-
if err != nil {
270-
return "", nil, nil, err
273+
if err := rows.Scan(&channelId, &gvkSQL); err != nil {
274+
return err
271275
}
272-
bundleBytes = append(bundleBytes, objBytes...)
273-
274-
if obj.GetObjectKind().GroupVersionKind().Kind == "ClusterServiceVersion" {
275-
csvName = obj.GetName()
276-
csvBytes, err = runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
277-
if err != nil {
278-
return "", nil, nil, err
279-
}
280-
csvCount += 1
281-
if csvCount > 1 {
282-
return "", nil, nil, fmt.Errorf("two csvs found in one bundle")
283-
}
276+
apigvk := []string{}
277+
if err := json.Unmarshal([]byte(gvkSQL.String), &apigvk); err != nil {
278+
return err
279+
}
280+
if _, err := addApi.Exec(apigvk[0], apigvk[1], apigvk[2]); err != nil {
281+
return err
282+
}
283+
if _, err := addApiProvider.Exec(apigvk[0], apigvk[1], apigvk[2], channelId.Int64); err != nil {
284+
return err
284285
}
285286
}
286287

287-
return csvName, csvBytes, bundleBytes, nil
288+
return tx.Commit()
288289
}
289290

290-
291+
func (s *SQLLoader) Close() {
292+
s.db.Close()
293+
}

‎pkg/sqlite/query.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,14 @@ import (
99
"github.com/operator-framework/operator-registry/pkg/registry"
1010
)
1111

12-
1312
type SQLQuerier struct {
1413
db *sql.DB
1514
}
1615

1716
var _ registry.Query = &SQLQuerier{}
1817

1918
func NewSQLLiteQuerier(dbFilename string) (*SQLQuerier, error) {
20-
db, err := sql.Open("sqlite3", "file:" + dbFilename + "?immutable=true")
19+
db, err := sql.Open("sqlite3", "file:"+dbFilename+"?immutable=true")
2120
if err != nil {
2221
return nil, err
2322
}

0 commit comments

Comments
 (0)
Please sign in to comment.