Skip to content

Commit 8415c18

Browse files
committedOct 24, 2019
feat(images): add support for storing and querying related images
this uses the `relatedImages` field from the CSV and also extracts operator images from the deployment specs of CSVs.
1 parent 274bf6b commit 8415c18

12 files changed

+536
-12
lines changed
 

‎configmap.example.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -5710,6 +5710,11 @@ data:
57105710
annotations:
57115711
tectonic-visibility: ocs
57125712
spec:
5713+
relatedImages:
5714+
- name: etcd-v3.4.0
5715+
image: quay.io/coreos/etcd@sha256:3816b6daf9b66d6ced6f0f966314e2d4f894982c6b1493061502f8c2bf86ac84
5716+
- name: etcd-3.4.1
5717+
image: quay.io/coreos/etcd@sha256:49d3d4a81e0d030d3f689e7167f23e120abf955f7d08dbedf3ea246485acee9f
57135718
displayName: etcd
57145719
description: |
57155720
etcd is a distributed key value store that provides a reliable way to store data across a cluster of machines. It’s open-source and available on GitHub. etcd gracefully handles leader elections during network partitions and will tolerate machine failure, including the leader. Your applications can read and write data into etcd.

‎manifests/etcd/0.9.2/etcdoperator.v0.9.2.clusterserviceversion.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ metadata:
88
tectonic-visibility: ocs
99
alm-examples: '[{"apiVersion":"etcd.database.coreos.com/v1beta2","kind":"EtcdCluster","metadata":{"name":"example","namespace":"default"},"spec":{"size":3,"version":"3.2.13"}},{"apiVersion":"etcd.database.coreos.com/v1beta2","kind":"EtcdRestore","metadata":{"name":"example-etcd-cluster"},"spec":{"etcdCluster":{"name":"example-etcd-cluster"},"backupStorageType":"S3","s3":{"path":"<full-s3-path>","awsSecret":"<aws-secret>"}}},{"apiVersion":"etcd.database.coreos.com/v1beta2","kind":"EtcdBackup","metadata":{"name":"example-etcd-cluster-backup"},"spec":{"etcdEndpoints":["<etcd-cluster-endpoints>"],"storageType":"S3","s3":{"path":"<full-s3-path>","awsSecret":"<aws-secret>"}}}]'
1010
spec:
11+
relatedImages:
12+
- name: etcd-v3.4.0
13+
image: quay.io/coreos/etcd@sha256:3816b6daf9b66d6ced6f0f966314e2d4f894982c6b1493061502f8c2bf86ac84
14+
- name: etcd-3.4.1
15+
image: quay.io/coreos/etcd@sha256:49d3d4a81e0d030d3f689e7167f23e120abf955f7d08dbedf3ea246485acee9f
1116
displayName: etcd
1217
description: |
1318
etcd is a distributed key value store that provides a reliable way to store data across a cluster of machines. It’s open-source and available on GitHub. etcd gracefully handles leader elections during network partitions and will tolerate machine failure, including the leader. Your applications can read and write data into etcd.

‎pkg/registry/bundle.go

+22
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,28 @@ func (b *Bundle) Serialize() (csvName string, csvBytes []byte, bundleBytes []byt
195195
return csvName, csvBytes, bundleBytes, nil
196196
}
197197

198+
func (b *Bundle) Images() (map[string]struct{}, error) {
199+
csv, err := b.ClusterServiceVersion()
200+
if err != nil {
201+
return nil, err
202+
}
203+
204+
images, err := csv.GetOperatorImages()
205+
if err != nil {
206+
return nil, err
207+
}
208+
209+
relatedImages, err := csv.GetRelatedImages()
210+
if err != nil {
211+
return nil, err
212+
}
213+
for img := range relatedImages {
214+
images[img] = struct{}{}
215+
}
216+
217+
return images, nil
218+
}
219+
198220
func (b *Bundle) cache() error {
199221
if !b.cacheStale {
200222
return nil

‎pkg/registry/csv.go

+75
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package registry
33
import (
44
"encoding/json"
55

6+
v1 "k8s.io/api/apps/v1"
67
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
78
)
89

@@ -28,6 +29,9 @@ const (
2829
// The yaml attribute that specifies the version of the ClusterServiceVersion
2930
// expected to be semver and parseable by blang/semver
3031
version = "version"
32+
33+
// The yaml attribute that specifies the related images of the ClusterServiceVersion
34+
relatedImages = "relatedImages"
3135
)
3236

3337
// ClusterServiceVersion is a structured representation of cluster service
@@ -187,3 +191,74 @@ func (csv *ClusterServiceVersion) GetApiServiceDefinitions() (owned []*Definitio
187191
required = definitions.Required
188192
return
189193
}
194+
195+
// GetRelatedImage returns the list of associated images for the operator
196+
func (csv *ClusterServiceVersion) GetRelatedImages() (imageSet map[string]struct{}, err error) {
197+
var objmap map[string]*json.RawMessage
198+
imageSet = make(map[string]struct{})
199+
200+
if err = json.Unmarshal(csv.Spec, &objmap); err != nil {
201+
return
202+
}
203+
204+
rawValue, ok := objmap[relatedImages]
205+
if !ok || rawValue == nil {
206+
return
207+
}
208+
209+
type relatedImage struct {
210+
Name string `json:"name"`
211+
Ref string `json:"image"`
212+
}
213+
var relatedImages []relatedImage
214+
if err = json.Unmarshal(*rawValue, &relatedImages); err != nil {
215+
return
216+
}
217+
218+
for _, img := range relatedImages {
219+
imageSet[img.Ref] = struct{}{}
220+
}
221+
222+
return
223+
}
224+
225+
// GetOperatorImages returns a list of any images used to run the operator.
226+
// Currently this pulls any images in the pod specs of operator deployments.
227+
func (csv *ClusterServiceVersion) GetOperatorImages() (map[string]struct{}, error) {
228+
type dep struct {
229+
Name string
230+
Spec v1.DeploymentSpec
231+
}
232+
type strategySpec struct {
233+
Deployments []dep
234+
}
235+
type strategy struct {
236+
Name string `json:"strategy"`
237+
Spec strategySpec `json:"spec"`
238+
}
239+
type csvSpec struct {
240+
Install strategy
241+
}
242+
243+
var spec csvSpec
244+
if err := json.Unmarshal(csv.Spec, &spec); err != nil {
245+
return nil, err
246+
}
247+
248+
// this is the only install strategy we know about
249+
if spec.Install.Name != "deployment" {
250+
return nil, nil
251+
}
252+
253+
images := map[string]struct{}{}
254+
for _, d := range spec.Install.Spec.Deployments {
255+
for _, c := range d.Spec.Template.Spec.Containers {
256+
images[c.Image] = struct{}{}
257+
}
258+
for _, c := range d.Spec.Template.Spec.InitContainers {
259+
images[c.Image] = struct{}{}
260+
}
261+
}
262+
263+
return images, nil
264+
}

‎pkg/registry/csv_test.go

+296
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"reflect"
66
"testing"
77

8+
"github.com/stretchr/testify/require"
89
"k8s.io/apimachinery/pkg/apis/meta/v1"
910
)
1011

@@ -464,3 +465,298 @@ func TestClusterServiceVersion_GetVersion(t *testing.T) {
464465
})
465466
}
466467
}
468+
469+
func TestClusterServiceVersion_GetRelatedImages(t *testing.T) {
470+
type fields struct {
471+
TypeMeta v1.TypeMeta
472+
ObjectMeta v1.ObjectMeta
473+
Spec json.RawMessage
474+
}
475+
tests := []struct {
476+
name string
477+
fields fields
478+
want map[string]struct{}
479+
wantErr bool
480+
}{
481+
{
482+
name: "no related images",
483+
fields: fields{
484+
TypeMeta: v1.TypeMeta{},
485+
ObjectMeta: v1.ObjectMeta{},
486+
Spec: json.RawMessage(`{"no": "field"}`),
487+
},
488+
want: map[string]struct{}{},
489+
},
490+
{
491+
name: "one related image",
492+
fields: fields{
493+
TypeMeta: v1.TypeMeta{},
494+
ObjectMeta: v1.ObjectMeta{},
495+
Spec: json.RawMessage(`{"relatedImages": [
496+
{"name": "test", "image": "quay.io/etcd/etcd-operator@sha256:123"}
497+
]}`),
498+
},
499+
want: map[string]struct{}{"quay.io/etcd/etcd-operator@sha256:123": {}},
500+
},
501+
{
502+
name: "multiple related images",
503+
fields: fields{
504+
TypeMeta: v1.TypeMeta{},
505+
ObjectMeta: v1.ObjectMeta{},
506+
Spec: json.RawMessage(`{"relatedImages": [
507+
{"name": "test", "image": "quay.io/etcd/etcd-operator@sha256:123"},
508+
{"name": "operand", "image": "quay.io/etcd/etcd@sha256:123"}
509+
]}`),
510+
},
511+
want: map[string]struct{}{"quay.io/etcd/etcd-operator@sha256:123": {}, "quay.io/etcd/etcd@sha256:123": {}},
512+
},
513+
}
514+
for _, tt := range tests {
515+
t.Run(tt.name, func(t *testing.T) {
516+
csv := &ClusterServiceVersion{
517+
TypeMeta: tt.fields.TypeMeta,
518+
ObjectMeta: tt.fields.ObjectMeta,
519+
Spec: tt.fields.Spec,
520+
}
521+
got, err := csv.GetRelatedImages()
522+
if (err != nil) != tt.wantErr {
523+
t.Errorf("GetRelatedImages() error = %v, wantErr %v", err, tt.wantErr)
524+
return
525+
}
526+
require.Equal(t, tt.want, got)
527+
})
528+
}
529+
}
530+
531+
func TestClusterServiceVersion_GetOperatorImages(t *testing.T) {
532+
type fields struct {
533+
TypeMeta v1.TypeMeta
534+
ObjectMeta v1.ObjectMeta
535+
Spec json.RawMessage
536+
}
537+
tests := []struct {
538+
name string
539+
fields fields
540+
want map[string]struct{}
541+
wantErr bool
542+
}{
543+
{
544+
name: "bad strategy",
545+
fields: fields{
546+
TypeMeta: v1.TypeMeta{},
547+
ObjectMeta: v1.ObjectMeta{},
548+
Spec: json.RawMessage(`
549+
{"install": {"strategy": "nope", "spec": {"deployments":[{"name":"etcd-operator","spec":{"template":{"spec":{"containers":[{
550+
"command":["etcd-operator"],
551+
"image":"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2",
552+
"name":"etcd-operator"
553+
}]}}}}]}}}`),
554+
},
555+
},
556+
{
557+
name: "no images",
558+
fields: fields{
559+
TypeMeta: v1.TypeMeta{},
560+
ObjectMeta: v1.ObjectMeta{},
561+
Spec: json.RawMessage(`
562+
{"install": {"strategy": "deployment","spec": {"deployments":[{"name":"etcd-operator","spec":{"template":{"spec":
563+
"containers":[]
564+
}}}}]}}}`),
565+
},
566+
want: nil,
567+
wantErr: true,
568+
},
569+
{
570+
name: "one image",
571+
fields: fields{
572+
TypeMeta: v1.TypeMeta{},
573+
ObjectMeta: v1.ObjectMeta{},
574+
Spec: json.RawMessage(`
575+
{"install": {"strategy": "deployment", "spec": {"deployments":[{
576+
"name":"etcd-operator",
577+
"spec":{
578+
"template":{
579+
"spec":{
580+
"containers":[
581+
{
582+
"command":["etcd-operator"],
583+
"image":"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2",
584+
"name":"etcd-operator"
585+
}
586+
]
587+
}
588+
}
589+
}}]}}}`),
590+
},
591+
want: map[string]struct{}{"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2":{}},
592+
},
593+
{
594+
name: "two container images",
595+
fields: fields{
596+
TypeMeta: v1.TypeMeta{},
597+
ObjectMeta: v1.ObjectMeta{},
598+
Spec: json.RawMessage(`
599+
{"install": {"strategy": "deployment", "spec": {"deployments":[{
600+
"name":"etcd-operator",
601+
"spec":{
602+
"template":{
603+
"spec":{
604+
"containers":[
605+
{
606+
"command":["etcd-operator"],
607+
"image":"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2",
608+
"name":"etcd-operator"
609+
},
610+
{
611+
"command":["etcd-operator-2"],
612+
"image":"quay.io/coreos/etcd-operator-2@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2",
613+
"name":"etcd-operator-2"
614+
}
615+
]
616+
}
617+
}
618+
}}]}}}`),
619+
},
620+
want: map[string]struct{}{"quay.io/coreos/etcd-operator-2@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2":{}, "quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2":{}},
621+
},
622+
{
623+
name: "init container image",
624+
fields: fields{
625+
TypeMeta: v1.TypeMeta{},
626+
ObjectMeta: v1.ObjectMeta{},
627+
Spec: json.RawMessage(`
628+
{
629+
"install": {
630+
"strategy": "deployment",
631+
"spec": {
632+
"deployments":[
633+
{
634+
"name":"etcd-operator",
635+
"spec":{
636+
"template":{
637+
"spec":{
638+
"initContainers":[
639+
{
640+
"command":["etcd-operator"],
641+
"image":"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2",
642+
"name":"etcd-operator"
643+
}
644+
]
645+
}
646+
}
647+
}
648+
}
649+
]
650+
}
651+
}
652+
}`),
653+
},
654+
want: map[string]struct{}{"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2":{}},
655+
},
656+
{
657+
name: "two init container images",
658+
fields: fields{
659+
TypeMeta: v1.TypeMeta{},
660+
ObjectMeta: v1.ObjectMeta{},
661+
Spec: json.RawMessage(`
662+
{
663+
"install": {
664+
"strategy": "deployment",
665+
"spec": {
666+
"deployments":[
667+
{
668+
"name":"etcd-operator",
669+
"spec":{
670+
"template":{
671+
"spec":{
672+
"initContainers":[
673+
{
674+
"command":["etcd-operator"],
675+
"image":"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2",
676+
"name":"etcd-operator"
677+
},
678+
{
679+
"command":["etcd-operator2"],
680+
"image":"quay.io/coreos/etcd-operator2@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2",
681+
"name":"etcd-operator2"
682+
}
683+
]
684+
}
685+
}
686+
}
687+
}
688+
]
689+
}
690+
}
691+
}`),
692+
},
693+
want: map[string]struct{}{"quay.io/coreos/etcd-operator2@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2":{}, "quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2":{}},
694+
},
695+
{
696+
name: "container and init container",
697+
fields: fields{
698+
TypeMeta: v1.TypeMeta{},
699+
ObjectMeta: v1.ObjectMeta{},
700+
Spec: json.RawMessage(`
701+
{
702+
"install": {
703+
"strategy": "deployment",
704+
"spec": {
705+
"deployments":[
706+
{
707+
"name":"etcd-operator",
708+
"spec":{
709+
"template":{
710+
"spec":{
711+
"initContainers":[
712+
{
713+
"command":["init-etcd-operator"],
714+
"image":"quay.io/coreos/init-etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2",
715+
"name":"etcd-operator"
716+
},
717+
{
718+
"command":["init-etcd-operator2"],
719+
"image":"quay.io/coreos/init-etcd-operator2@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2",
720+
"name":"etcd-operator2"
721+
}
722+
],
723+
"containers":[
724+
{
725+
"command":["etcd-operator"],
726+
"image":"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2",
727+
"name":"etcd-operator"
728+
},
729+
{
730+
"command":["etcd-operator2"],
731+
"image":"quay.io/coreos/etcd-operator2@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2",
732+
"name":"etcd-operator2"
733+
}
734+
]
735+
}
736+
}
737+
}
738+
}
739+
]
740+
}
741+
}
742+
}`),
743+
},
744+
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":{}},
745+
},
746+
}
747+
for _, tt := range tests {
748+
t.Run(tt.name, func(t *testing.T) {
749+
csv := &ClusterServiceVersion{
750+
TypeMeta: tt.fields.TypeMeta,
751+
ObjectMeta: tt.fields.ObjectMeta,
752+
Spec: tt.fields.Spec,
753+
}
754+
got, err := csv.GetOperatorImages()
755+
if (err != nil) != tt.wantErr {
756+
t.Errorf("GetOperatorImages() error = %v, wantErr %v", err, tt.wantErr)
757+
return
758+
}
759+
require.Equal(t, tt.want, got)
760+
})
761+
}
762+
}

‎pkg/registry/empty.go

+11
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,17 @@ func (EmptyQuery) GetBundleThatProvides(ctx context.Context, group, version, kin
5050
return "", nil, errors.New("empty querier: cannot get bundle that provides")
5151
}
5252

53+
func (EmptyQuery) ListImages(ctx context.Context) ([]string, error) {
54+
return nil, errors.New("empty querier: cannot get image list")
55+
56+
}
57+
58+
func (EmptyQuery) GetImagesForBundle(ctx context.Context, bundleName string) ([]string, error) {
59+
return nil, errors.New("empty querier: cannot get image list")
60+
}
61+
62+
var _ Query = &EmptyQuery{}
63+
5364
func NewEmptyQuerier() *EmptyQuery {
5465
return &EmptyQuery{}
5566
}

‎pkg/registry/interface.go

+4
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,8 @@ type Query interface {
2525
GetLatestChannelEntriesThatProvide(ctx context.Context, group, version, kind string) (entries []*ChannelEntry, err error)
2626
// Get the the latest bundle that provides the API in a default channel
2727
GetBundleThatProvides(ctx context.Context, group, version, kind string) (string, *ChannelEntry, error)
28+
// List all images in the database
29+
ListImages(ctx context.Context) ([]string, error)
30+
// List all images for a particular bundle
31+
GetImagesForBundle(ctx context.Context, bundleName string) ([]string, error)
2832
}

‎pkg/server/server_test.go

+12-11
Large diffs are not rendered by default.

‎pkg/sqlite/configmap_test.go

+23
Original file line numberDiff line numberDiff line change
@@ -129,4 +129,27 @@ func TestQuerierForConfigmap(t *testing.T) {
129129
require.NoError(t, err)
130130
require.Equal(t, expectedBundle, etcdBundleByProvides)
131131
require.Equal(t, &registry.ChannelEntry{"etcd", "alpha","etcdoperator.v0.9.2", ""}, entry)
132+
133+
expectedEtcdImages := []string{
134+
"quay.io/coreos/etcd-operator@sha256:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943",
135+
"quay.io/coreos/etcd@sha256:3816b6daf9b66d6ced6f0f966314e2d4f894982c6b1493061502f8c2bf86ac84",
136+
"quay.io/coreos/etcd@sha256:49d3d4a81e0d030d3f689e7167f23e120abf955f7d08dbedf3ea246485acee9f",
137+
}
138+
etcdImages, err := store.GetImagesForBundle(context.TODO(), "etcdoperator.v0.6.1")
139+
require.NoError(t, err)
140+
require.ElementsMatch(t, expectedEtcdImages, etcdImages)
141+
142+
expectedDatabaseImages := []string{
143+
"quay.io/coreos/etcd@sha256:49d3d4a81e0d030d3f689e7167f23e120abf955f7d08dbedf3ea246485acee9f",
144+
"quay.io/coreos/etcd-operator@sha256:bd944a211eaf8f31da5e6d69e8541e7cada8f16a9f7a5a570b22478997819943",
145+
"quay.io/coreos/etcd@sha256:3816b6daf9b66d6ced6f0f966314e2d4f894982c6b1493061502f8c2bf86ac84",
146+
"quay.io/coreos/etcd-operator@sha256:db563baa8194fcfe39d1df744ed70024b0f1f9e9b55b5923c2f3a413c44dc6b8",
147+
"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2",
148+
"quay.io/coreos/prometheus-operator@sha256:5037b4e90dbb03ebdefaa547ddf6a1f748c8eeebeedf6b9d9f0913ad662b5731",
149+
"quay.io/coreos/prometheus-operator@sha256:0e92dd9b5789c4b13d53e1319d0a6375bcca4caaf0d698af61198061222a576d",
150+
"quay.io/coreos/prometheus-operator@sha256:3daa69a8c6c2f1d35dcf1fe48a7cd8b230e55f5229a1ded438f687debade5bcf",
151+
}
152+
dbImages, err := store.ListImages(context.TODO())
153+
require.NoError(t, err)
154+
require.ElementsMatch(t, expectedDatabaseImages, dbImages)
132155
}

‎pkg/sqlite/directory_test.go

+28-1
Large diffs are not rendered by default.

‎pkg/sqlite/load.go

+17
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ func (s *SQLLoader) AddOperatorBundle(bundle *registry.Bundle) error {
6363
}
6464
defer stmt.Close()
6565

66+
addImage, err := tx.Prepare("insert into related_image(image, operatorbundle_name) values(?,?)")
67+
if err != nil {
68+
return err
69+
}
70+
defer addImage.Close()
71+
6672
csvName, csvBytes, bundleBytes, err := bundle.Serialize()
6773
if err != nil {
6874
return err
@@ -76,6 +82,17 @@ func (s *SQLLoader) AddOperatorBundle(bundle *registry.Bundle) error {
7682
return err
7783
}
7884

85+
imgs, err := bundle.Images()
86+
if err != nil {
87+
return err
88+
}
89+
// TODO: bulk insert
90+
for img := range imgs {
91+
if _, err := addImage.Exec(img, csvName); err != nil {
92+
return err
93+
}
94+
}
95+
7996
return tx.Commit()
8097
}
8198

‎pkg/sqlite/query.go

+38
Original file line numberDiff line numberDiff line change
@@ -315,3 +315,41 @@ func (s *SQLQuerier) GetBundleThatProvides(ctx context.Context, group, version,
315315
}
316316
return bundle.String, entry, nil
317317
}
318+
319+
func (s *SQLQuerier) ListImages(ctx context.Context) ([]string, error){
320+
query := "SELECT DISTINCT image FROM related_image"
321+
rows, err := s.db.QueryContext(ctx, query)
322+
if err != nil {
323+
return nil, err
324+
}
325+
images := []string{}
326+
for rows.Next() {
327+
var imgName sql.NullString
328+
if err := rows.Scan(&imgName); err != nil {
329+
return nil, err
330+
}
331+
if imgName.Valid {
332+
images = append(images, imgName.String)
333+
}
334+
}
335+
return images, nil
336+
}
337+
338+
func (s *SQLQuerier) GetImagesForBundle(ctx context.Context, csvName string) ([]string, error) {
339+
query := "SELECT DISTINCT image FROM related_image WHERE operatorbundle_name=?"
340+
rows, err := s.db.QueryContext(ctx, query, csvName)
341+
if err != nil {
342+
return nil, err
343+
}
344+
images := []string{}
345+
for rows.Next() {
346+
var imgName sql.NullString
347+
if err := rows.Scan(&imgName); err != nil {
348+
return nil, err
349+
}
350+
if imgName.Valid {
351+
images = append(images, imgName.String)
352+
}
353+
}
354+
return images, nil
355+
}

0 commit comments

Comments
 (0)
Please sign in to comment.