forked from rook/rook
Remove the AWS SDK v1 (github.com/aws/aws-sdk-go) dependency entirely. All S3 operations now use AWS SDK v2 exclusively. - Remove the v1 Client field from S3Agent struct and rename ClientV2 to Client - Remove v1 session/client initialization from NewS3Agent - Update all call sites referencing ClientV2 - Convert integration tests to use v2 API calling conventions (context parameter) and smithy error handling - Remove aws-sdk-go v1.55.8 from go.mod Signed-off-by: Oded Viner <oviner@redhat.com>
217 lines
7.8 KiB
Go
217 lines
7.8 KiB
Go
/*
|
|
Copyright 2021 The Rook Authors. All rights reserved.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
// Package notification to manage a rook bucket notifications.
|
|
package notification
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
|
s3types "github.com/aws/aws-sdk-go-v2/service/s3/types"
|
|
"github.com/ceph/go-ceph/rgw/admin"
|
|
"github.com/coreos/pkg/capnslog"
|
|
bktv1alpha1 "github.com/kube-object-storage/lib-bucket-provisioner/pkg/apis/objectbucket.io/v1alpha1"
|
|
"github.com/pkg/errors"
|
|
cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
|
|
"github.com/rook/rook/pkg/clusterd"
|
|
cephclient "github.com/rook/rook/pkg/daemon/ceph/client"
|
|
"github.com/rook/rook/pkg/operator/ceph/object"
|
|
"github.com/rook/rook/pkg/operator/ceph/object/bucket"
|
|
"github.com/rook/rook/pkg/util/log"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
)
|
|
|
|
type provisioner struct {
|
|
context *clusterd.Context
|
|
clusterInfo *cephclient.ClusterInfo
|
|
clusterSpec *cephv1.ClusterSpec
|
|
opManagerContext context.Context
|
|
owner string
|
|
objectStoreName types.NamespacedName
|
|
}
|
|
|
|
func getUserCredentials(adminOpsCtx *object.AdminOpsContext, opManagerContext context.Context, username string) (accessKey string, secretKey string, err error) {
|
|
if len(username) == 0 {
|
|
err = errors.New("no user name provided")
|
|
return
|
|
}
|
|
|
|
var u admin.User
|
|
u, err = adminOpsCtx.AdminOpsClient.GetUser(opManagerContext, admin.User{ID: username})
|
|
if err != nil {
|
|
err = errors.Wrapf(err, "failed to get ceph user %q", username)
|
|
return
|
|
}
|
|
|
|
logger.Infof("successfully fetched ceph user %q", username)
|
|
accessKey = u.Keys[0].AccessKey
|
|
secretKey = u.Keys[0].SecretKey
|
|
return
|
|
}
|
|
|
|
func newS3Agent(p provisioner) (*object.S3Agent, error) {
|
|
objStore, err := p.context.RookClientset.CephV1().CephObjectStores(p.objectStoreName.Namespace).Get(p.opManagerContext, p.objectStoreName.Name, metav1.GetOptions{})
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to get CephObjectStore %v", p.objectStoreName)
|
|
}
|
|
|
|
objContext, err := object.NewMultisiteContext(p.context, p.clusterInfo, objStore)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to get object context for CephObjectStore %v", p.objectStoreName)
|
|
}
|
|
|
|
adminOpsCtx, err := object.NewMultisiteAdminOpsContext(objContext, &objStore.Spec)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to get admin Ops context for CephObjectStore %q", p.objectStoreName)
|
|
}
|
|
accessKey, secretKey, err := getUserCredentials(adminOpsCtx, p.opManagerContext, p.owner)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to get owner credentials for %q", p.owner)
|
|
}
|
|
tlsCert := make([]byte, 0)
|
|
insecureTLS := false
|
|
if objStore.Spec.IsTLSEnabled() {
|
|
tlsCert, insecureTLS, err = object.GetTlsCaCert(objContext, &objStore.Spec)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to fetch TLS certificate for the object store")
|
|
}
|
|
}
|
|
|
|
return object.NewS3Agent(accessKey, secretKey, objContext.Endpoint, logger.LevelAt(capnslog.DEBUG), tlsCert, insecureTLS, nil)
|
|
}
|
|
|
|
// TODO: convert all rules without restrictions once the AWS SDK supports that
|
|
func createS3FilterRules(filterRules []cephv1.NotificationKeyFilterRule) (s3FilterRules []s3types.FilterRule) {
|
|
for _, rule := range filterRules {
|
|
r := rule.DeepCopy()
|
|
s3FilterRules = append(s3FilterRules, s3types.FilterRule{
|
|
Name: s3types.FilterRuleName(r.Name),
|
|
Value: &r.Value,
|
|
})
|
|
}
|
|
return
|
|
}
|
|
|
|
func createS3Filter(filter *cephv1.NotificationFilterSpec) *s3types.NotificationConfigurationFilter {
|
|
if filter == nil {
|
|
return nil
|
|
}
|
|
return &s3types.NotificationConfigurationFilter{
|
|
Key: &s3types.S3KeyFilter{
|
|
FilterRules: createS3FilterRules(filter.KeyFilters),
|
|
},
|
|
}
|
|
}
|
|
|
|
func createS3Events(events []cephv1.BucketNotificationEvent) []s3types.Event {
|
|
// in the AWS S3 library "Events" is required field
|
|
// but in our CR it is optional. indicating notifications on all events
|
|
if len(events) == 0 {
|
|
return []s3types.Event{
|
|
s3types.Event("s3:ObjectCreated:*"),
|
|
s3types.Event("s3:ObjectRemoved:*"),
|
|
}
|
|
}
|
|
s3Events := make([]s3types.Event, 0, len(events))
|
|
for _, event := range events {
|
|
s3Events = append(s3Events, s3types.Event(string(event)))
|
|
}
|
|
return s3Events
|
|
}
|
|
|
|
// Allow overriding this function for unit tests
|
|
var createNotificationFunc = createNotification
|
|
|
|
var createNotification = func(p provisioner, bucket *bktv1alpha1.ObjectBucket, topicARN string, notification *cephv1.CephBucketNotification) error {
|
|
bucketName := bucket.Spec.Endpoint.BucketName
|
|
bnName := types.NamespacedName{Namespace: notification.Namespace, Name: notification.Name}
|
|
s3Agent, err := newS3Agent(p)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to create S3 agent for CephBucketNotification %q provisioning for bucket %q", bnName, bucketName)
|
|
}
|
|
_, err = s3Agent.Client.PutBucketNotificationConfiguration(p.opManagerContext, &s3.PutBucketNotificationConfigurationInput{
|
|
Bucket: &bucketName,
|
|
NotificationConfiguration: &s3types.NotificationConfiguration{
|
|
TopicConfigurations: []s3types.TopicConfiguration{
|
|
{
|
|
Events: createS3Events(notification.Spec.Events),
|
|
Filter: createS3Filter(notification.Spec.Filter),
|
|
Id: ¬ification.Name,
|
|
TopicArn: &topicARN,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to provisioning CephBucketNotification %q for bucket %q", bnName, bucketName)
|
|
}
|
|
|
|
log.NamedInfo(bnName, logger, "CephBucketNotification was created for bucket %q", bucketName)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Allow overriding this function for unit tests
|
|
var getAllNotificationsFunc = getAllRGWNotifications
|
|
|
|
var getAllRGWNotifications = func(p provisioner, ob *bktv1alpha1.ObjectBucket) ([]string, error) {
|
|
bucketName := ob.Spec.Endpoint.BucketName
|
|
ownerName := ob.Spec.AdditionalState[bucket.CephUser]
|
|
s3Agent, err := newS3Agent(p)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to create S3 agent for CephBucketNotification provisioning for bucket %q", bucketName)
|
|
}
|
|
nc, err := s3Agent.Client.GetBucketNotificationConfiguration(p.opManagerContext, &s3.GetBucketNotificationConfigurationInput{
|
|
Bucket: &bucketName,
|
|
ExpectedBucketOwner: &ownerName,
|
|
})
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to get BucketNotification from bucket %q", bucketName)
|
|
}
|
|
|
|
notificationList := make([]string, 0)
|
|
if nc != nil {
|
|
for _, tc := range nc.TopicConfigurations {
|
|
notificationList = append(notificationList, *tc.Id)
|
|
}
|
|
}
|
|
logger.Debugf("Bucket Notifications %q was listed for bucket %q", notificationList, bucketName)
|
|
|
|
return notificationList, nil
|
|
}
|
|
|
|
// Allow overriding this function for unit tests
|
|
var deleteNotificationFunc = deleteNotification
|
|
|
|
var deleteNotification = func(p provisioner, bucket *bktv1alpha1.ObjectBucket, notificationId string) error {
|
|
bucketName := types.NamespacedName{Namespace: bucket.Namespace, Name: bucket.Name}
|
|
s3Agent, err := newS3Agent(p)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to create S3 agent for deleting all bucket notifications from bucket %q", bucketName)
|
|
}
|
|
if err := DeleteBucketNotification(context.TODO(), s3Agent.Client, &DeleteBucketNotificationRequestInput{
|
|
Bucket: &bucket.Spec.Endpoint.BucketName,
|
|
}, notificationId); err != nil {
|
|
return errors.Wrapf(err, "failed to delete bucket notification %q from bucket %q", notificationId, bucketName)
|
|
}
|
|
|
|
logger.Debugf("bucket notification %q deleted from bucket %q", notificationId, bucketName)
|
|
|
|
return nil
|
|
}
|