Skip to content

Commit

Permalink
Merge pull request #203 from RHEcosystemAppEng/main
Browse files Browse the repository at this point in the history
Merge `main` into `release-0.2.0`
  • Loading branch information
tchughesiv authored Jul 11, 2022
2 parents 3d9216f + d6d4b83 commit 0f2e772
Show file tree
Hide file tree
Showing 3 changed files with 254 additions and 47 deletions.
40 changes: 37 additions & 3 deletions api/v1alpha1/dbaasinventory_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ import (
"sigs.k8s.io/controller-runtime/pkg/webhook"
)

const (
rdsRegistration = "rds-registration"
providerNameKey = "spec.providerRef.name"
)

// log is for logging in this package.
var dbaasinventorylog = logf.Log.WithName("dbaasinventory-resource")
var inventoryWebhookApiClient client.Client = nil
Expand All @@ -38,6 +43,13 @@ func (r *DBaaSInventory) SetupWebhookWithManager(mgr ctrl.Manager) error {
if inventoryWebhookApiClient == nil {
inventoryWebhookApiClient = mgr.GetClient()
}
// index inventory by `spec.providerRef.name`
if err := mgr.GetFieldIndexer().IndexField(context.Background(), &DBaaSInventory{}, providerNameKey, func(rawObj client.Object) []string {
inventory := rawObj.(*DBaaSInventory)
return []string{inventory.Spec.ProviderRef.Name}
}); err != nil {
return err
}
return ctrl.NewWebhookManagedBy(mgr).
For(r).
Complete()
Expand All @@ -50,13 +62,13 @@ var _ webhook.Validator = &DBaaSInventory{}
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (r *DBaaSInventory) ValidateCreate() error {
dbaasinventorylog.Info("validate create", "name", r.Name)
return validateInventory(r)
return validateInventory(r, nil)
}

// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
func (r *DBaaSInventory) ValidateUpdate(old runtime.Object) error {
dbaasinventorylog.Info("validate update", "name", r.Name)
return validateInventory(r)
return validateInventory(r, old.(*DBaaSInventory))
}

// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
Expand All @@ -65,7 +77,12 @@ func (r *DBaaSInventory) ValidateDelete() error {
return nil
}

func validateInventory(inv *DBaaSInventory) error {
func validateInventory(inv *DBaaSInventory, oldInv *DBaaSInventory) error {
// Provider name is immutable
if oldInv != nil && oldInv.Spec.ProviderRef.Name != inv.Spec.ProviderRef.Name {
msg := "provider name is immutable for provider accounts"
return field.Invalid(field.NewPath("spec").Child("providerRef").Child("name"), inv.Spec.ProviderRef.Name, msg)
}
// Retrieve the secret object
secret := &corev1.Secret{}
if err := inventoryWebhookApiClient.Get(context.TODO(), types.NamespacedName{Name: inv.Spec.DBaaSInventorySpec.CredentialsRef.Name, Namespace: inv.Namespace}, secret); err != nil {
Expand All @@ -76,6 +93,12 @@ func validateInventory(inv *DBaaSInventory) error {
if err := inventoryWebhookApiClient.Get(context.TODO(), types.NamespacedName{Name: inv.Spec.ProviderRef.Name, Namespace: ""}, provider); err != nil {
return err
}
// Check RDS
if oldInv == nil && inv.Spec.ProviderRef.Name == rdsRegistration {
if err := validateRDS(); err != nil {
return err
}
}
return validateInventoryMandatoryFields(inv, secret, provider)
}

Expand All @@ -91,3 +114,14 @@ func validateInventoryMandatoryFields(inv *DBaaSInventory, secret *corev1.Secret
}
return nil
}

func validateRDS() error {
rdsInventoryList := &DBaaSInventoryList{}
if err := inventoryWebhookApiClient.List(context.TODO(), rdsInventoryList, client.MatchingFields{providerNameKey: rdsRegistration}); err != nil {
return err
}
if len(rdsInventoryList.Items) > 0 {
return fmt.Errorf("only one provider account for RDS can exist in a cluster, but there is already a provider account %s created", rdsInventoryList.Items[0].Name)
}
return nil
}
211 changes: 171 additions & 40 deletions api/v1alpha1/dbaasinventory_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,26 @@ import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)

const (
testSecretName = "testsecret"
testSecretNameUpdate = "testsecretupdate"
testProviderName = "mongodb-atlas"
testInventoryKind = "MongoDBAtlasInventory"
testConnectionKind = "MongoDBAtlasConnection"
testInstaneKind = "MongoDBAtlasInstance"
testSecretName = "testsecret"
testSecretNameUpdate = "testsecretupdate"
testProviderName = "mongodb-atlas"
testInventoryKind = "MongoDBAtlasInventory"
testConnectionKind = "MongoDBAtlasConnection"
testInstanceKind = "MongoDBAtlasInstance"
testSecretNameRDS = "testsecretrds"
testInventoryKindRDS = "RDSInventory"
testConnectionKindRDS = "RDSConnection"
testInstanceKindRDS = "RDSInstance"
awsAccessKeyID = "AWS_ACCESS_KEY_ID"
awsSecretAccessKey = "AWS_SECRET_ACCESS_KEY" //#nosec G101
awsRegion = "AWS_REGION"
ackResourceTags = "ACK_RESOURCE_TAGS"
ackLogLevel = "ACK_LOG_LEVEL"
)

var (
Expand All @@ -46,7 +54,7 @@ var (
},
InventoryKind: testInventoryKind,
ConnectionKind: testConnectionKind,
InstanceKind: testInstaneKind,
InstanceKind: testInstanceKind,
CredentialFields: []CredentialField{
{
Key: "field1",
Expand All @@ -65,6 +73,56 @@ var (
InstanceParameterSpecs: []InstanceParameterSpec{},
},
}
testProviderRDS = DBaaSProvider{
ObjectMeta: metav1.ObjectMeta{
Name: rdsRegistration,
Namespace: testNamespace,
},
Spec: DBaaSProviderSpec{
Provider: DatabaseProvider{
Name: rdsRegistration,
},
InventoryKind: testInventoryKindRDS,
ConnectionKind: testConnectionKindRDS,
InstanceKind: testInstanceKindRDS,
CredentialFields: []CredentialField{
{
Key: awsAccessKeyID,
DisplayName: "AWS Access Key ID",
Type: "maskedstring",
Required: true,
},
{
Key: awsSecretAccessKey,
DisplayName: "AWS Secret Access Key",
Type: "maskedstring",
Required: true,
},
{
Key: awsRegion,
DisplayName: "AWS Region",
Type: "string",
Required: true,
},
{
Key: ackResourceTags,
DisplayName: "ACK Resource Tags",
Type: "string",
Required: false,
},
{
Key: ackLogLevel,
DisplayName: "ACK Log Level",
Type: "string",
Required: false,
},
},
AllowsFreeTrial: true,
ExternalProvisionURL: "",
ExternalProvisionDescription: "",
InstanceParameterSpecs: []InstanceParameterSpec{},
},
}
testSecret = v1.Secret{
TypeMeta: metav1.TypeMeta{
Kind: "Opaque",
Expand Down Expand Up @@ -129,6 +187,20 @@ var (
"field1": []byte("test1"),
},
}
testSecretRDS = v1.Secret{
TypeMeta: metav1.TypeMeta{
Kind: "Opaque",
},
ObjectMeta: metav1.ObjectMeta{
Name: testSecretNameRDS,
Namespace: testNamespace,
},
Data: map[string][]byte{
"AWS_ACCESS_KEY_ID": []byte("myaccesskeyid"),
"AWS_SECRET_ACCESS_KEY": []byte("myaccesskey"),
"AWS_REGION": []byte("myregion"),
},
}
testDBaaSInventory = DBaaSInventory{
ObjectMeta: metav1.ObjectMeta{
Name: inventoryName,
Expand Down Expand Up @@ -245,37 +317,96 @@ var _ = Describe("DBaaSInventory Webhook", func() {
Expect(err).Should(MatchError("admission webhook \"vdbaasinventory.kb.io\" denied the request: spec.credentialsRef: Invalid value: v1alpha1.LocalObjectReference{Name:\"testsecretupdate\"}: credentialsRef is invalid: field1 is required in secret testsecretupdate"))
})
})
It("update fails with provider name change", func() {
inv := testDBaaSInventory.DeepCopy()
Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(inv), inv)).Should(Succeed())
inv.Spec.ProviderRef.Name = "crunchy-registration"
err := k8sClient.Update(ctx, inv)
Expect(err).Should(MatchError("admission webhook \"vdbaasinventory.kb.io\" denied the request: spec.providerRef.name: Invalid value: \"crunchy-registration\": provider name is immutable for provider accounts"))
})
})
Context("After creating DBaaSInventory for RDS", func() {
testSecretRDS2 := v1.Secret{
TypeMeta: metav1.TypeMeta{
Kind: "Opaque",
},
ObjectMeta: metav1.ObjectMeta{
Name: testSecretNameRDS,
Namespace: testNamespace2,
},
Data: map[string][]byte{
"AWS_ACCESS_KEY_ID": []byte("myaccesskeyid"),
"AWS_SECRET_ACCESS_KEY": []byte("myaccesskey"),
"AWS_REGION": []byte("myregion"),
},
}
testDBaaSInventoryRDS := DBaaSInventory{
ObjectMeta: metav1.ObjectMeta{
Name: "test-inventory-rds",
Namespace: testNamespace,
},
Spec: DBaaSOperatorInventorySpec{
ProviderRef: NamespacedName{
Name: rdsRegistration,
},
DBaaSInventorySpec: DBaaSInventorySpec{
CredentialsRef: &LocalObjectReference{
Name: testSecretNameRDS,
},
},
},
}
BeforeEach(assertResourceCreation(&testSecretRDS2))
BeforeEach(assertResourceCreation(&testSecretRDS))
BeforeEach(assertResourceCreation(&testProviderRDS))
BeforeEach(assertResourceCreation(&testDBaaSInventoryRDS))
AfterEach(assertResourceDeletion(&testDBaaSInventoryRDS))
AfterEach(assertResourceDeletion(&testProviderRDS))
AfterEach(assertResourceDeletion(&testSecretRDS))
AfterEach(assertResourceDeletion(&testSecretRDS2))
Context("creating another DBaaSInventory for RDS", func() {
It("should not allow creating DBaaSInventory for RDS in the same namespace", func() {
testDBaaSInventoryRDSNotAllowed := DBaaSInventory{
ObjectMeta: metav1.ObjectMeta{
Name: "test-inventory-rds-not-allowed",
Namespace: testNamespace,
},
Spec: DBaaSOperatorInventorySpec{
ProviderRef: NamespacedName{
Name: rdsRegistration,
},
DBaaSInventorySpec: DBaaSInventorySpec{
CredentialsRef: &LocalObjectReference{
Name: testSecretNameRDS,
},
},
},
}
By("creating DBaaSInventory for RDS")
Expect(k8sClient.Create(ctx, &testDBaaSInventoryRDSNotAllowed)).Should(MatchError("admission webhook \"vdbaasinventory.kb.io\" denied the request:" +
" only one provider account for RDS can exist in a cluster, but there is already a provider account test-inventory-rds created"))
})
It("should not allow creating DBaaSInventory for RDS in another namespace", func() {
testDBaaSInventoryRDSNotAllowed := DBaaSInventory{
ObjectMeta: metav1.ObjectMeta{
Name: "test-inventory-rds-not-allowed",
Namespace: testNamespace2,
},
Spec: DBaaSOperatorInventorySpec{
ProviderRef: NamespacedName{
Name: rdsRegistration,
},
DBaaSInventorySpec: DBaaSInventorySpec{
CredentialsRef: &LocalObjectReference{
Name: testSecretNameRDS,
},
},
},
}
err := k8sClient.Create(ctx, &testDBaaSInventoryRDSNotAllowed)
Expect(err).Should(MatchError("admission webhook \"vdbaasinventory.kb.io\" denied the request:" +
" only one provider account for RDS can exist in a cluster, but there is already a provider account test-inventory-rds created"))
})
})
})
})

func assertResourceCreation(object client.Object) func() {
return func() {
By("creating resource")
object.SetResourceVersion("")
Expect(k8sClient.Create(ctx, object)).Should(Succeed())

By("checking the resource created")
Eventually(func() bool {
if err := k8sClient.Get(ctx, client.ObjectKeyFromObject(object), object); err != nil {
return false
}
return true
}, timeout, interval).Should(BeTrue())
}
}

func assertResourceDeletion(object client.Object) func() {
return func() {
By("deleting resource")
Expect(k8sClient.Delete(ctx, object)).Should(Succeed())

By("checking the resource deleted")
Eventually(func() bool {
err := k8sClient.Get(ctx, client.ObjectKeyFromObject(object), object)
if err != nil && errors.IsNotFound(err) {
return true
}
return false
}, timeout, interval).Should(BeTrue())
}
}
Loading

0 comments on commit 0f2e772

Please sign in to comment.