Skip to content

Commit

Permalink
Merge branch 'main' into rs_decomission
Browse files Browse the repository at this point in the history
  • Loading branch information
mshaposhnik authored May 29, 2024
2 parents e5effd5 + 4577b7f commit e5984ca
Show file tree
Hide file tree
Showing 14 changed files with 467 additions and 7 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Build the manager binary
# For more details and updates, refer to
# https://catalog.redhat.com/software/containers/ubi9/go-toolset/61e5c00b4ec9945c18787690
FROM registry.access.redhat.com/ubi9/go-toolset:1.20.10 as builder
FROM registry.access.redhat.com/ubi9/go-toolset:1.21.9-1 as builder

# Copy the Go Modules manifests
COPY go.mod go.mod
Expand Down
45 changes: 45 additions & 0 deletions api/v1alpha1/imagerepository_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ type ImageRepositorySpec struct {
// Credentials management.
// +optional
Credentials *ImageCredentials `json:"credentials,omitempty"`

// Notifications defines configuration for image repository notifications.
// +optional
Notifications []Notifications `json:"notifications,omitempty"`
}

// ImageParameters describes requested image repository configuration.
Expand Down Expand Up @@ -62,6 +66,37 @@ type ImageCredentials struct {
RegenerateToken *bool `json:"regenerate-token,omitempty"`
}

type Notifications struct {
Title string `json:"title,omitempty"`
// +kubebuilder:validation:Enum=repo_push
Event NotificationEvent `json:"event,omitempty"`
// +kubebuilder:validation:Enum=email;webhook
Method NotificationMethod `json:"method,omitempty"`
Config NotificationConfig `json:"config,omitempty"`
}

type NotificationEvent string

const (
NotificationEventRepoPush NotificationEvent = "repo_push"
)

type NotificationMethod string

const (
NotificationMethodEmail NotificationMethod = "email"
NotificationMethodWebhook NotificationMethod = "webhook"
)

type NotificationConfig struct {
// Email is the email address to send notifications to.
// +optional
Email string `json:"email,omitempty"`
// Webhook is the URL to send notifications to.
// +optional
Url string `json:"url,omitempty"`
}

// ImageRepositoryStatus defines the observed state of ImageRepository
type ImageRepositoryStatus struct {
// State shows if image repository could be used.
Expand All @@ -79,6 +114,10 @@ type ImageRepositoryStatus struct {

// Credentials contain information related to image repository credentials.
Credentials CredentialsStatus `json:"credentials,omitempty"`

// Notifications shows the status of the notifications configuration.
// +optional
Notifications []NotificationStatus `json:"notifications,omitempty"`
}

type ImageRepositoryState string
Expand Down Expand Up @@ -119,6 +158,12 @@ type CredentialsStatus struct {
PullRobotAccountName string `json:"pull-robot-account,omitempty"`
}

// NotificationStatus shows the status of the notification configuration.
type NotificationStatus struct {
Title string `json:"title,omitempty"`
UUID string `json:"uuid,omitempty"`
}

//+kubebuilder:object:root=true
//+kubebuilder:subresource:status

Expand Down
56 changes: 56 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 40 additions & 0 deletions config/crd/bases/appstudio.redhat.com_imagerepositories.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,34 @@ spec:
- private
type: string
type: object
notifications:
description: Notifications defines configuration for image repository
notifications.
items:
properties:
config:
properties:
email:
description: Email is the email address to send notifications
to.
type: string
url:
description: Webhook is the URL to send notifications to.
type: string
type: object
event:
enum:
- repo_push
type: string
method:
enum:
- email
- webhook
type: string
title:
type: string
type: object
type: array
type: object
status:
description: ImageRepositoryStatus defines the observed state of ImageRepository
Expand Down Expand Up @@ -130,6 +158,18 @@ spec:
contain non critical error, like failed to change image visibility,
while the state is ready and image resitory could be used.
type: string
notifications:
description: Notifications shows the status of the notifications configuration.
items:
description: NotificationStatus shows the status of the notification
configuration.
properties:
title:
type: string
uuid:
type: string
type: object
type: array
state:
description: State shows if image repository could be used. "ready"
means repository was created and usable, "failed" means that the
Expand Down
51 changes: 51 additions & 0 deletions controllers/imagerepository_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,51 @@ func (r *ImageRepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Requ
return ctrl.Result{}, nil
}

func (r *ImageRepositoryReconciler) AddNotifications(ctx context.Context, imageRepository *imagerepositoryv1alpha1.ImageRepository) ([]imagerepositoryv1alpha1.NotificationStatus, error) {
log := ctrllog.FromContext(ctx).WithName("ConfigureNotifications")

if imageRepository.Spec.Notifications == nil {
// No notifications to configure
return nil, nil
}

log.Info("Configuring notifications")
notificationStatus := []imagerepositoryv1alpha1.NotificationStatus{}

for _, notification := range imageRepository.Spec.Notifications {
log.Info("Creating notification in Quay", "Title", notification.Title, "Event", notification.Event, "Method", notification.Method)
quayNotification, err := r.QuayClient.CreateNotification(
r.QuayOrganization,
imageRepository.Spec.Image.Name,
quay.Notification{
Title: notification.Title,
Event: string(notification.Event),
Method: string(notification.Method),
Config: quay.NotificationConfig{
Url: notification.Config.Url,
},
EventConfig: quay.NotificationEventConfig{},
})
if err != nil {
log.Error(err, "failed to create notification", "Title", notification.Title, "Event", notification.Event, "Method", notification.Method)
return nil, err
}
notificationStatus = append(
notificationStatus,
imagerepositoryv1alpha1.NotificationStatus{
UUID: quayNotification.UUID,
Title: notification.Title,
})

log.Info("Notification added",
"Title", notification.Title,
"Event", notification.Event,
"Method", notification.Method,
"QuayNotification", quayNotification)
}
return notificationStatus, nil
}

// ProvisionImageRepository creates image repository, robot account(s) and secret(s) to access the image repository.
// If labels with Application and Component name are present, robot account with pull only access
// will be created and pull token will be propagated to all environments via Remote Secret.
Expand Down Expand Up @@ -318,6 +363,11 @@ func (r *ImageRepositoryReconciler) ProvisionImageRepository(ctx context.Context
}
}

var notificationStatus []imagerepositoryv1alpha1.NotificationStatus
if notificationStatus, err = r.AddNotifications(ctx, imageRepository); err != nil {
return err
}

status := imagerepositoryv1alpha1.ImageRepositoryStatus{}
status.State = imagerepositoryv1alpha1.ImageRepositoryStateReady
status.Image.URL = quayImageURL
Expand All @@ -329,6 +379,7 @@ func (r *ImageRepositoryReconciler) ProvisionImageRepository(ctx context.Context
status.Credentials.PullRobotAccountName = pullCredentialsInfo.RobotAccountName
status.Credentials.PullSecretName = pullCredentialsInfo.SecretName
}
status.Notifications = notificationStatus

imageRepository.Spec.Image.Name = imageRepositoryName
controllerutil.AddFinalizer(imageRepository, ImageRepositoryFinalizer)
Expand Down
29 changes: 29 additions & 0 deletions controllers/imagerepository_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,19 @@ var _ = Describe("Image repository controller", func() {
return nil
}

isCreateNotificationInvoked := false
quay.CreateNotificationFunc = func(organization, repository string, notification quay.Notification) (*quay.Notification, error) {
isCreateNotificationInvoked = true
Expect(organization).To(Equal(quay.TestQuayOrg))
return &quay.Notification{UUID: "uuid"}, nil
}

createImageRepository(imageRepositoryConfig{})

Eventually(func() bool { return isCreateRepositoryInvoked }, timeout, interval).Should(BeTrue())
Eventually(func() bool { return isCreateRobotAccountInvoked }, timeout, interval).Should(BeTrue())
Eventually(func() bool { return isAddPushPermissionsToRobotAccountInvoked }, timeout, interval).Should(BeTrue())
Eventually(func() bool { return isCreateNotificationInvoked }, timeout, interval).Should(BeFalse())

waitImageRepositoryFinalizerOnImageRepository(resourceKey)

Expand All @@ -111,6 +119,7 @@ var _ = Describe("Image repository controller", func() {
Expect(imageRepository.Status.Credentials.PushRobotAccountName).To(HavePrefix(expectedRobotAccountPrefix))
Expect(imageRepository.Status.Credentials.PushSecretName).To(Equal(imageRepository.Name + "-image-push"))
Expect(imageRepository.Status.Credentials.GenerationTimestamp).ToNot(BeNil())
Expect(imageRepository.Status.Notifications).To(HaveLen(0))

pushSecretKey := types.NamespacedName{Name: imageRepository.Status.Credentials.PushSecretName, Namespace: imageRepository.Namespace}
pushSecret := waitSecretExist(pushSecretKey)
Expand Down Expand Up @@ -300,12 +309,28 @@ var _ = Describe("Image repository controller", func() {
}
return nil
}
isCreateNotificationInvoked := false
quay.CreateNotificationFunc = func(organization, repository string, notification quay.Notification) (*quay.Notification, error) {
isCreateNotificationInvoked = true
Expect(organization).To(Equal(quay.TestQuayOrg))
return &quay.Notification{UUID: "uuid"}, nil
}

imageRepositoryConfigObject := imageRepositoryConfig{
Labels: map[string]string{
ApplicationNameLabelName: defaultComponentApplication,
ComponentNameLabelName: defaultComponentName,
},
Notifications: []imagerepositoryv1alpha1.Notifications{
{
Title: "test-notification",
Event: imagerepositoryv1alpha1.NotificationEventRepoPush,
Method: imagerepositoryv1alpha1.NotificationMethodWebhook,
Config: imagerepositoryv1alpha1.NotificationConfig{
Url: "http://test-url",
},
},
},
}

if updateComponentAnnotation {
Expand All @@ -319,6 +344,7 @@ var _ = Describe("Image repository controller", func() {
Eventually(func() bool { return isCreatePullRobotAccountInvoked }, timeout, interval).Should(BeTrue())
Eventually(func() bool { return isAddPushPermissionsToRobotAccountInvoked }, timeout, interval).Should(BeTrue())
Eventually(func() bool { return isAddPullPermissionsToRobotAccountInvoked }, timeout, interval).Should(BeTrue())
Eventually(func() bool { return isCreateNotificationInvoked }, timeout, interval).Should(BeTrue())

waitImageRepositoryFinalizerOnImageRepository(resourceKey)

Expand Down Expand Up @@ -348,6 +374,9 @@ var _ = Describe("Image repository controller", func() {
Expect(imageRepository.Status.Credentials.PullRobotAccountName).To(HaveSuffix("_pull"))
Expect(imageRepository.Status.Credentials.PullSecretName).To(Equal(imageRepository.Name + "-image-pull"))
Expect(imageRepository.Status.Credentials.GenerationTimestamp).ToNot(BeNil())
Expect(imageRepository.Status.Notifications).To(HaveLen(1))
Expect(imageRepository.Status.Notifications[0].UUID).To(Equal("uuid"))
Expect(imageRepository.Status.Notifications[0].Title).To(Equal("test-notification"))

pushSecretKey := types.NamespacedName{Name: imageRepository.Status.Credentials.PushSecretName, Namespace: imageRepository.Namespace}
pushSecret := waitSecretExist(pushSecretKey)
Expand Down
12 changes: 7 additions & 5 deletions controllers/suite_util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,12 @@ const (
)

type imageRepositoryConfig struct {
ResourceKey *types.NamespacedName
ImageName string
Visibility string
Labels map[string]string
Annotations map[string]string
ResourceKey *types.NamespacedName
ImageName string
Visibility string
Labels map[string]string
Annotations map[string]string
Notifications []imagerepositoryv1alpha1.Notifications
}

func getImageRepositoryConfig(config imageRepositoryConfig) *imagerepositoryv1alpha1.ImageRepository {
Expand Down Expand Up @@ -89,6 +90,7 @@ func getImageRepositoryConfig(config imageRepositoryConfig) *imagerepositoryv1al
Name: config.ImageName,
Visibility: imagerepositoryv1alpha1.ImageVisibility(visibility),
},
Notifications: config.Notifications,
},
}
}
Expand Down
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
module github.com/konflux-ci/image-controller

go 1.20
go 1.21

toolchain go1.21.9

require (
github.com/go-logr/logr v1.4.1
Expand Down
Loading

0 comments on commit e5984ca

Please sign in to comment.