Skip to content

Commit 6e77d75

Browse files
authored
Support self-hosted Quay registries (#276)
Signed-off-by: Mykola Morhun <mmorhun@redhat.com>
1 parent 0489fa4 commit 6e77d75

8 files changed

Lines changed: 74 additions & 50 deletions

File tree

README.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
[![codecov](https://codecov.io/gh/konflux-ci/image-controller/branch/main/graph/badge.svg)](https://codecov.io/gh/konflux-ci/image-controller)
2-
# The Image Controller for AppStudio
3-
The Image Controller operator helps set up container image repositories on Quay.io for AppStudio.
2+
# The Image Repository Controller for Konflux
3+
The Image Controller operator helps set up container image repositories on Quay image registry for Konflux.
44

55
### Image Controller operator installation
66

77
1. Install the project on your cluster by running `make deploy`.
8-
2. Set up the Quay.io token that would be used by the controller to set up image repositories under the `quay.io/redhat-user-workloads` organization.
8+
2. Set up the Quay.io token that would be used by the controller to set up image repositories under the `quay.io/user-workloads` organization.
99

1010
```
1111
kind: Secret
@@ -14,7 +14,8 @@ metadata:
1414
name: quaytoken
1515
namespace: image-controller-system
1616
data:
17-
organization: redhat-user-workloads
17+
quayapiurl: https://quay.io/api/v1
18+
organization: user-workloads
1819
quaytoken: redacted
1920
type: Opaque
2021
```
@@ -154,7 +155,7 @@ in Quay will get deleted as well.
154155

155156
In order to skip the deletion of the repository in Quay, the `image-controller.appstudio.redhat.com/skip-repository-deletion` annotation should be set to "true".
156157

157-
## AppStudio Component image repository
158+
## Konflux Component image repository
158159

159160
### Image repository for Component builds
160161

@@ -270,7 +271,7 @@ The `Image controller` would create the necessary resources on Quay.io and write
270271

271272
```json
272273
{
273-
"image":"quay.io/redhat-user-workloads/image-controller-system/city-transit/billing",
274+
"image":"quay.io/user-workloads/image-controller-system/city-transit/billing",
274275
"visibility":"public",
275276
"secret":"billing"
276277
}
@@ -283,7 +284,7 @@ metadata:
283284
annotations:
284285
image.redhat.com/generate: 'false'
285286
image.redhat.com/image: >-
286-
{"image":"quay.io/redhat-user-workloads/image-controller-system/city-transit/billing","visibility":"public","secret":"billing"}
287+
{"image":"quay.io/user-workloads/image-controller-system/city-transit/billing","visibility":"public","secret":"billing"}
287288
name: billing
288289
namespace: image-controller-system
289290
resourceVersion: '86424'
@@ -295,7 +296,7 @@ spec:
295296

296297
## License
297298

298-
Copyright 2023.
299+
Copyright 2023-2026.
299300

300301
Licensed under the Apache License, Version 2.0 (the "License");
301302
you may not use this file except in compliance with the License.

cmd/main.go

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package main
1818

1919
import (
2020
"crypto/tls"
21+
"errors"
2122
"flag"
2223
"fmt"
2324
"net/http"
@@ -46,7 +47,6 @@ import (
4647
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
4748
"sigs.k8s.io/controller-runtime/pkg/webhook"
4849

49-
"github.com/go-logr/logr"
5050
imagerepositoryv1alpha1 "github.com/konflux-ci/image-controller/api/v1alpha1"
5151
controllers "github.com/konflux-ci/image-controller/internal/controller"
5252
controllermetrics "github.com/konflux-ci/image-controller/pkg/metrics"
@@ -56,6 +56,7 @@ import (
5656
)
5757

5858
const (
59+
quayApiPath string = "/workspace/quayapiurl"
5960
/* #nosec it's the path to the token, not the token itself */
6061
quayTokenPath string = "/workspace/quaytoken"
6162
quayOrgPath string = "/workspace/organization"
@@ -179,26 +180,43 @@ func main() {
179180
os.Exit(1)
180181
}
181182

182-
readConfig := func(l logr.Logger, path string) string {
183+
// Read Quay config (from the mounted secret)
184+
readConfig := func(path string) (string, error) {
183185
/* #nosec we are sure the input path is clean */
184186
tokenContent, err := os.ReadFile(path)
185187
if err != nil {
186-
l.Error(err, fmt.Sprintf("unable to read %s", path))
188+
return "", fmt.Errorf("unable to read %s: %w", path, err)
187189
}
188-
return strings.TrimSpace(string(tokenContent))
190+
return strings.TrimSpace(string(tokenContent)), nil
189191
}
190-
quayOrganization := readConfig(setupLog, quayOrgPath)
191-
buildQuayClientFunc := func(l logr.Logger) quay.QuayService {
192-
token := readConfig(l, quayTokenPath)
193-
quayClient := quay.NewQuayClient(&http.Client{Transport: &http.Transport{}}, token, "https://quay.io/api/v1")
194-
return quayClient
192+
quayApiUrl, err := readConfig(quayApiPath)
193+
if err != nil {
194+
if !errors.Is(err, os.ErrNotExist) {
195+
setupLog.Error(err, "unable to read Quay API URL")
196+
os.Exit(2)
197+
}
198+
// Quay API URL is not set, fall back to default
199+
quayApiUrl = "https://quay.io/api/v1"
200+
}
201+
setupLog.Info(fmt.Sprintf("using Quay API URL: %s", quayApiUrl))
202+
quayOrganization, err := readConfig(quayOrgPath)
203+
if err != nil {
204+
setupLog.Error(err, "unable to read Quay Org")
205+
os.Exit(2)
206+
}
207+
setupLog.Info(fmt.Sprintf("using Quay Org: %s", quayOrganization))
208+
buildQuayClientFunc := func() (quay.QuayService, error) {
209+
token, err := readConfig(quayTokenPath)
210+
if err != nil {
211+
return nil, fmt.Errorf("unable to get quay token: %w", err)
212+
}
213+
quayClient := quay.NewQuayClient(&http.Client{Transport: &http.Transport{}}, token, quayApiUrl)
214+
return quayClient, nil
195215
}
196216

197217
if err = (&controllers.ComponentReconciler{
198-
Client: mgr.GetClient(),
199-
Scheme: mgr.GetScheme(),
200-
BuildQuayClient: buildQuayClientFunc,
201-
QuayOrganization: quayOrganization,
218+
Client: mgr.GetClient(),
219+
Scheme: mgr.GetScheme(),
202220
}).SetupWithManager(mgr); err != nil {
203221
setupLog.Error(err, "unable to create controller", "controller", "Controller")
204222
os.Exit(1)

internal/controller/component_image_controller.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,8 @@ import (
3131
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
3232
ctrllog "sigs.k8s.io/controller-runtime/pkg/log"
3333

34-
"github.com/go-logr/logr"
3534
imagerepositoryv1alpha1 "github.com/konflux-ci/image-controller/api/v1alpha1"
3635
l "github.com/konflux-ci/image-controller/pkg/logs"
37-
"github.com/konflux-ci/image-controller/pkg/quay"
3836
appstudioredhatcomv1alpha1 "github.com/redhat-appstudio/application-api/api/v1alpha1"
3937
)
4038

@@ -63,9 +61,6 @@ type ImageRepositoryStatus struct {
6361
type ComponentReconciler struct {
6462
client.Client
6563
Scheme *runtime.Scheme
66-
67-
BuildQuayClient func(logr.Logger) quay.QuayService
68-
QuayOrganization string
6964
}
7065

7166
// SetupWithManager sets up the controller with the Manager.

internal/controller/imagerepository_controller.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import (
2727
"strings"
2828
"time"
2929

30-
"github.com/go-logr/logr"
3130
imagerepositoryv1alpha1 "github.com/konflux-ci/image-controller/api/v1alpha1"
3231
l "github.com/konflux-ci/image-controller/pkg/logs"
3332
"github.com/konflux-ci/image-controller/pkg/metrics"
@@ -70,7 +69,7 @@ type ImageRepositoryReconciler struct {
7069
Scheme *runtime.Scheme
7170

7271
QuayClient quay.QuayService
73-
BuildQuayClient func(logr.Logger) quay.QuayService
72+
BuildQuayClient func() (quay.QuayService, error)
7473
QuayOrganization string
7574
}
7675

@@ -120,7 +119,10 @@ func (r *ImageRepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Requ
120119
delete(metrics.RepositoryTimesForMetrics, repositoryIdForMetrics)
121120

122121
// Reread quay token
123-
r.QuayClient = r.BuildQuayClient(log)
122+
r.QuayClient, err = r.BuildQuayClient()
123+
if err != nil {
124+
return ctrl.Result{}, err
125+
}
124126

125127
if isComponentLinked(imageRepository) {
126128
// unlink secret from component SA
@@ -185,7 +187,10 @@ func (r *ImageRepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Requ
185187
}
186188

187189
// Reread quay token
188-
r.QuayClient = r.BuildQuayClient(log)
190+
r.QuayClient, err = r.BuildQuayClient()
191+
if err != nil {
192+
return ctrl.Result{}, err
193+
}
189194

190195
// Provision image repository if it hasn't been done yet
191196
if !controllerutil.ContainsFinalizer(imageRepository, ImageRepositoryFinalizer) {

internal/controller/suite_test.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,23 +106,21 @@ var _ = BeforeSuite(func() {
106106
err = (&ImageRepositoryReconciler{
107107
Client: k8sManager.GetClient(),
108108
Scheme: k8sManager.GetScheme(),
109-
BuildQuayClient: func(l logr.Logger) quay.QuayService { return quay.TestQuayClient{} },
109+
BuildQuayClient: func() (quay.QuayService, error) { return quay.TestQuayClient{}, nil },
110110
QuayOrganization: quay.TestQuayOrg,
111111
}).SetupWithManager(k8sManager)
112112
Expect(err).ToNot(HaveOccurred())
113113

114114
err = (&ComponentReconciler{
115-
Client: k8sManager.GetClient(),
116-
Scheme: k8sManager.GetScheme(),
117-
BuildQuayClient: func(logr.Logger) quay.QuayService { return quay.TestQuayClient{} },
118-
QuayOrganization: quay.TestQuayOrg,
115+
Client: k8sManager.GetClient(),
116+
Scheme: k8sManager.GetScheme(),
119117
}).SetupWithManager(k8sManager)
120118
Expect(err).ToNot(HaveOccurred())
121119

122120
err = (&QuayUsersConfigMapReconciler{
123121
Client: k8sManager.GetClient(),
124122
Scheme: k8sManager.GetScheme(),
125-
BuildQuayClient: func(l logr.Logger) quay.QuayService { return quay.TestQuayClient{} },
123+
BuildQuayClient: func() (quay.QuayService, error) { return quay.TestQuayClient{}, nil },
126124
QuayOrganization: quay.TestQuayOrg,
127125
}).SetupWithManager(k8sManager)
128126
Expect(err).ToNot(HaveOccurred())

internal/controller/users_config_map_controller.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121
"fmt"
2222
"strings"
2323

24-
"github.com/go-logr/logr"
2524
corev1 "k8s.io/api/core/v1"
2625
"k8s.io/apimachinery/pkg/api/errors"
2726
"k8s.io/apimachinery/pkg/runtime"
@@ -48,7 +47,7 @@ type QuayUsersConfigMapReconciler struct {
4847
Scheme *runtime.Scheme
4948

5049
QuayClient quay.QuayService
51-
BuildQuayClient func(logr.Logger) quay.QuayService
50+
BuildQuayClient func() (quay.QuayService, error)
5251
QuayOrganization string
5352
}
5453

@@ -144,7 +143,11 @@ func (r *QuayUsersConfigMapReconciler) Reconcile(ctx context.Context, req ctrl.R
144143
}
145144

146145
// reread quay token
147-
r.QuayClient = r.BuildQuayClient(log)
146+
var err error
147+
r.QuayClient, err = r.BuildQuayClient()
148+
if err != nil {
149+
return ctrl.Result{}, err
150+
}
148151

149152
// remove team if config map is being removed, or doesn't contain key additionalUsersConfigMapKey
150153
if removeTeam {

pkg/metrics/metrics_test.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"testing"
66

7-
"github.com/go-logr/logr"
87
"github.com/konflux-ci/image-controller/pkg/quay"
98
"github.com/prometheus/client_golang/prometheus"
109
"github.com/prometheus/client_golang/prometheus/testutil"
@@ -36,7 +35,7 @@ func TestRegisterMetrics(t *testing.T) {
3635
})
3736
}
3837

39-
func getTestClient(logger logr.Logger) quay.QuayService {
38+
func getTestClient() (quay.QuayService, error) {
4039
quay.ResetTestQuayClient()
41-
return &quay.TestQuayClient{}
40+
return &quay.TestQuayClient{}, nil
4241
}

pkg/metrics/quay.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,25 @@ import (
44
"context"
55
"fmt"
66

7-
"github.com/go-logr/logr"
87
"github.com/konflux-ci/image-controller/pkg/quay"
98
"github.com/prometheus/client_golang/prometheus"
10-
ctrllog "sigs.k8s.io/controller-runtime/pkg/log"
119
)
1210

1311
type QuayAvailabilityProbe struct {
14-
BuildQuayClient func(logr.Logger) quay.QuayService
12+
BuildQuayClient func() (quay.QuayService, error)
1513
QuayOrganization string
1614
gauge prometheus.Gauge
1715
}
1816

1917
const testRobotAccountName = "robot_konflux_api_healthcheck"
2018

21-
func NewQuayAvailabilityProbe(ctx context.Context, clientBuilder func(logr.Logger) quay.QuayService, quayOrganization string) (*QuayAvailabilityProbe, error) {
22-
client := clientBuilder(ctrllog.FromContext(ctx))
23-
_, err := client.CreateRobotAccount(quayOrganization, testRobotAccountName)
19+
func NewQuayAvailabilityProbe(ctx context.Context, clientBuilder func() (quay.QuayService, error), quayOrganization string) (*QuayAvailabilityProbe, error) {
20+
client, err := clientBuilder()
21+
if err != nil {
22+
return nil, fmt.Errorf("could not create quay client: %w", err)
23+
}
24+
25+
_, err = client.CreateRobotAccount(quayOrganization, testRobotAccountName)
2426
if err != nil {
2527
return nil, fmt.Errorf("could not create test robot account: %w", err)
2628
}
@@ -38,8 +40,11 @@ func NewQuayAvailabilityProbe(ctx context.Context, clientBuilder func(logr.Logge
3840
}
3941

4042
func (q *QuayAvailabilityProbe) CheckAvailability(ctx context.Context) error {
41-
client := q.BuildQuayClient(ctrllog.FromContext(ctx))
42-
_, err := client.GetRobotAccount(q.QuayOrganization, testRobotAccountName)
43+
client, err := q.BuildQuayClient()
44+
if err != nil {
45+
return fmt.Errorf("could not create quay client: %w", err)
46+
}
47+
_, err = client.GetRobotAccount(q.QuayOrganization, testRobotAccountName)
4348
return err
4449
}
4550

0 commit comments

Comments
 (0)