From 57e312c3c4793ac3f79acfbc5eb9bb3dfdedb58a Mon Sep 17 00:00:00 2001 From: raffis Date: Mon, 12 Jun 2023 09:54:22 +0200 Subject: [PATCH] feat: add support for custom grants (#39) --- Makefile | 2 +- api/v1beta1/postgresqluser_type.go | 18 + api/v1beta1/zz_generated.deepcopy.go | 32 + common/database/postgresql.go | 95 +- ...ning.infra.doodle.com_postgresqlusers.yaml | 25 + controllers/mongodb_test.go | 1202 ++++++++--------- controllers/postgresql_test.go | 1120 ++++++++------- controllers/postgresqluser_controller.go | 35 +- go.mod | 8 +- go.sum | 16 +- 10 files changed, 1353 insertions(+), 1200 deletions(-) diff --git a/Makefile b/Makefile index ebf5f55..4e5dbdd 100644 --- a/Makefile +++ b/Makefile @@ -65,7 +65,7 @@ lint: golangci-lint ## Run golangci-lint against code .PHONY: test test: manifests generate fmt vet tidy envtest ## Run tests. - KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test ./... -v -coverprofile coverage.out -race + KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test ./... -v -coverprofile coverage.out -race ##@ Build diff --git a/api/v1beta1/postgresqluser_type.go b/api/v1beta1/postgresqluser_type.go index f31b06b..a1bd15f 100644 --- a/api/v1beta1/postgresqluser_type.go +++ b/api/v1beta1/postgresqluser_type.go @@ -20,12 +20,30 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +type Privilege string + +var SelectPrivilege Privilege = "SELECT" +var AlPrivilege Privilege = "ALL" + type PostgreSQLUserSpec struct { // +required Database *DatabaseReference `json:"database"` // +required Credentials *SecretReference `json:"credentials"` + + // +kubebuilder:default:={{privileges: {ALL}, object: SCHEMA, objectName: public}} + Grants []Grant `json:"grants,omitempty"` + + // Roles are postgres roles granted to this user + Roles []string `json:"roles,omitempty"` +} + +type Grant struct { + Object string `json:"object,omitempty"` + ObjectName string `json:"objectName,omitempty"` + User string `json:"user,omitempty"` + Privileges []Privilege `json:"privileges,omitempty"` } // GetStatusConditions returns a pointer to the Status.Conditions slice diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 3a562c4..6fbf6d6 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -95,6 +95,26 @@ func (in Extensions) DeepCopy() Extensions { return *out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Grant) DeepCopyInto(out *Grant) { + *out = *in + if in.Privileges != nil { + in, out := &in.Privileges, &out.Privileges + *out = make([]Privilege, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Grant. +func (in *Grant) DeepCopy() *Grant { + if in == nil { + return nil + } + out := new(Grant) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MongoDBDatabase) DeepCopyInto(out *MongoDBDatabase) { *out = *in @@ -504,6 +524,18 @@ func (in *PostgreSQLUserSpec) DeepCopyInto(out *PostgreSQLUserSpec) { *out = new(SecretReference) **out = **in } + if in.Grants != nil { + in, out := &in.Grants, &out.Grants + *out = make([]Grant, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Roles != nil { + in, out := &in.Roles, &out.Roles + *out = make([]string, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgreSQLUserSpec. diff --git a/common/database/postgresql.go b/common/database/postgresql.go index 5426107..9adb7e0 100644 --- a/common/database/postgresql.go +++ b/common/database/postgresql.go @@ -94,21 +94,47 @@ func (s *PostgreSQLRepository) CreateDatabaseIfNotExists(ctx context.Context, da } } -func (s *PostgreSQLRepository) SetupUser(ctx context.Context, database string, user string, password string) error { +type PostgresqlUser struct { + Database string + Username string + Password string + Roles []string + Grants []Grant +} + +type Grant struct { + Object string + ObjectName string + User string + Privileges []Privilege +} + +type Privilege string + +var SelectPrivilege Privilege = "SELECT" +var AlPrivilege Privilege = "ALL" + +func (s *PostgreSQLRepository) SetupUser(ctx context.Context, user PostgresqlUser) error { if err := s.createUserIfNotExists(ctx, user); err != nil { - return err + return fmt.Errorf("failed to create user: %w", err) } - if err := s.setPasswordForUser(ctx, user, password); err != nil { - return err + if err := s.setPasswordForUser(ctx, user); err != nil { + return fmt.Errorf("failed to set password: %w", err) } - if err := s.grantAllPrivileges(ctx, database, user); err != nil { - return err + if err := s.grantAllPrivileges(ctx, user); err != nil { + return fmt.Errorf("failed to grant all privileges: %w", err) + } + if err := s.grantRoles(ctx, user); err != nil { + return fmt.Errorf("failed to grant roles: %w", err) + } + if err := s.grantRules(ctx, user); err != nil { + return fmt.Errorf("failed to apply grant rules: %w", err) } return nil } -func (s *PostgreSQLRepository) DropUser(ctx context.Context, database string, user string) error { - if err := s.RevokeAllPrivileges(ctx, database, user); err != nil { +func (s *PostgreSQLRepository) DropUser(ctx context.Context, user PostgresqlUser) error { + if err := s.RevokeAllPrivileges(ctx, user); err != nil { return err } if err := s.dropUserIfNotExist(ctx, user); err != nil { @@ -126,14 +152,14 @@ func (s *PostgreSQLRepository) EnableExtension(ctx context.Context, db, name str return nil } -func (s *PostgreSQLRepository) createUserIfNotExists(ctx context.Context, user string) error { +func (s *PostgreSQLRepository) createUserIfNotExists(ctx context.Context, user PostgresqlUser) error { if userExists, err := s.doesUserExist(ctx, user); err != nil { return err } else { if userExists { return nil } - if _, err := s.conn.Exec(ctx, fmt.Sprintf("CREATE USER %s;", (pgx.Identifier{user}).Sanitize())); err != nil { + if _, err := s.conn.Exec(ctx, fmt.Sprintf("CREATE USER %s;", (pgx.Identifier{user.Username}).Sanitize())); err != nil { return err } else { if userExistsNow, err := s.doesUserExist(ctx, user); err != nil { @@ -154,14 +180,14 @@ func (s *PostgreSQLRepository) createExtension(ctx context.Context, db, name str return err } -func (s *PostgreSQLRepository) dropUserIfNotExist(ctx context.Context, user string) error { +func (s *PostgreSQLRepository) dropUserIfNotExist(ctx context.Context, user PostgresqlUser) error { if userExists, err := s.doesUserExist(ctx, user); err != nil { return err } else { if !userExists { return nil } - if _, err := s.conn.Exec(ctx, fmt.Sprintf("DROP USER %s;", (pgx.Identifier{user}).Sanitize())); err != nil { + if _, err := s.conn.Exec(ctx, fmt.Sprintf("DROP USER %s;", (pgx.Identifier{user.Username}).Sanitize())); err != nil { return err } else { if userExistsNow, err := s.doesUserExist(ctx, user); err != nil { @@ -177,23 +203,48 @@ func (s *PostgreSQLRepository) dropUserIfNotExist(ctx context.Context, user stri } } -func (s *PostgreSQLRepository) setPasswordForUser(ctx context.Context, user string, password string) error { - password, err := s.conn.PgConn().EscapeString(password) +func (s *PostgreSQLRepository) setPasswordForUser(ctx context.Context, user PostgresqlUser) error { + password, err := s.conn.PgConn().EscapeString(user.Password) if err != nil { return err } - _, err = s.conn.Exec(ctx, fmt.Sprintf("ALTER USER %s WITH ENCRYPTED PASSWORD '%s';", (pgx.Identifier{user}).Sanitize(), password)) + _, err = s.conn.Exec(ctx, fmt.Sprintf("ALTER USER %s WITH ENCRYPTED PASSWORD '%s';", (pgx.Identifier{user.Username}).Sanitize(), password)) return err } -func (s *PostgreSQLRepository) grantAllPrivileges(ctx context.Context, database string, user string) error { - _, err := s.conn.Exec(ctx, fmt.Sprintf("GRANT ALL PRIVILEGES ON DATABASE %s TO %s;", (pgx.Identifier{database}).Sanitize(), (pgx.Identifier{user}).Sanitize())) +func (s *PostgreSQLRepository) grantAllPrivileges(ctx context.Context, user PostgresqlUser) error { + _, err := s.conn.Exec(ctx, fmt.Sprintf("GRANT ALL PRIVILEGES ON DATABASE %s TO %s;", (pgx.Identifier{user.Database}).Sanitize(), (pgx.Identifier{user.Username}).Sanitize())) return err } -func (s *PostgreSQLRepository) RevokeAllPrivileges(ctx context.Context, database string, user string) error { - _, err := s.conn.Exec(ctx, fmt.Sprintf("REVOKE ALL PRIVILEGES ON DATABASE %s FROM %s;", (pgx.Identifier{database}).Sanitize(), (pgx.Identifier{user}).Sanitize())) +func (s *PostgreSQLRepository) grantRoles(ctx context.Context, user PostgresqlUser) error { + for _, role := range user.Roles { + _, err := s.conn.Exec(ctx, fmt.Sprintf("GRANT %s TO %s;", (pgx.Identifier{role}).Sanitize(), (pgx.Identifier{user.Username}).Sanitize())) + if err != nil { + return err + } + } + + return nil +} + +func (s *PostgreSQLRepository) grantRules(ctx context.Context, user PostgresqlUser) error { + for _, grant := range user.Grants { + for _, p := range grant.Privileges { + _, err := s.conn.Exec(ctx, fmt.Sprintf("GRANT %s ON %s %s TO %s;", string(p), grant.Object, (pgx.Identifier{grant.ObjectName}).Sanitize(), (pgx.Identifier{user.Username}).Sanitize())) + if err != nil { + return err + } + } + + } + + return nil +} + +func (s *PostgreSQLRepository) RevokeAllPrivileges(ctx context.Context, user PostgresqlUser) error { + _, err := s.conn.Exec(ctx, fmt.Sprintf("REVOKE ALL PRIVILEGES ON DATABASE %s FROM %s;", (pgx.Identifier{user.Database}).Sanitize(), (pgx.Identifier{user.Username}).Sanitize())) return err } @@ -214,14 +265,14 @@ func (s *PostgreSQLRepository) doesDatabaseExist(ctx context.Context, database s return result == 1, nil } -func (s *PostgreSQLRepository) doesUserExist(ctx context.Context, user string) (bool, error) { - user, err := s.conn.PgConn().EscapeString(user) +func (s *PostgreSQLRepository) doesUserExist(ctx context.Context, user PostgresqlUser) (bool, error) { + username, err := s.conn.PgConn().EscapeString(user.Username) if err != nil { return false, err } var result int64 - err = s.conn.QueryRow(ctx, fmt.Sprintf("SELECT 1 FROM pg_roles WHERE rolname='%s';", user)).Scan(&result) + err = s.conn.QueryRow(ctx, fmt.Sprintf("SELECT 1 FROM pg_roles WHERE rolname='%s';", username)).Scan(&result) if err != nil { if err == pgx.ErrNoRows { return false, nil diff --git a/config/base/crd/bases/dbprovisioning.infra.doodle.com_postgresqlusers.yaml b/config/base/crd/bases/dbprovisioning.infra.doodle.com_postgresqlusers.yaml index bfb12f4..b4fbf0e 100644 --- a/config/base/crd/bases/dbprovisioning.infra.doodle.com_postgresqlusers.yaml +++ b/config/base/crd/bases/dbprovisioning.infra.doodle.com_postgresqlusers.yaml @@ -77,6 +77,31 @@ spec: required: - name type: object + grants: + default: + - object: SCHEMA + objectName: public + privileges: + - ALL + items: + properties: + object: + type: string + objectName: + type: string + privileges: + items: + type: string + type: array + user: + type: string + type: object + type: array + roles: + description: Roles are postgres roles granted to this user + items: + type: string + type: array required: - credentials - database diff --git a/controllers/mongodb_test.go b/controllers/mongodb_test.go index b66d559..8ba784d 100644 --- a/controllers/mongodb_test.go +++ b/controllers/mongodb_test.go @@ -43,9 +43,9 @@ type mongodbContainer struct { URI string } -func setupMongoDBContainer(ctx context.Context) (*mongodbContainer, error) { +func setupMongoDBContainer(ctx context.Context, image string) (*mongodbContainer, error) { req := testcontainers.ContainerRequest{ - Image: "mongo:4.4", + Image: image, ExposedPorts: []string{"27017/tcp"}, WaitingFor: wait.ForListeningPort("27017"), Env: map[string]string{ @@ -65,17 +65,12 @@ func setupMongoDBContainer(ctx context.Context) (*mongodbContainer, error) { return nil, err } - ip, err := container.Host(ctx) + ip, err := container.ContainerIP(ctx) if err != nil { return nil, err } - mappedPort, err := container.MappedPort(ctx, "27017") - if err != nil { - return nil, err - } - - uri := fmt.Sprintf("mongodb://%s:%s", ip, mappedPort.Port()) + uri := fmt.Sprintf("mongodb://%s:27017", ip) return &mongodbContainer{Container: container, URI: uri}, nil } @@ -86,640 +81,645 @@ var _ = Describe("MongoDB", func() { interval = time.Second * 1 ) - var ( - container *mongodbContainer - err error - ) - - container, err = setupMongoDBContainer(context.TODO()) - Expect(err).NotTo(HaveOccurred(), "failed to start mongodb container") - - Describe("fails if database not found", Ordered, func() { - var ( - createdUser *infrav1beta1.MongoDBUser - keyUser types.NamespacedName - ) - - namespace, _ := setupNamespace() - - It("creates user", func() { - keyUser = types.NamespacedName{ - Name: "mongodbuser-" + randStringRunes(5), - Namespace: namespace.Name, - } - createdUser = &infrav1beta1.MongoDBUser{ - ObjectMeta: metav1.ObjectMeta{ - Name: keyUser.Name, - Namespace: keyUser.Namespace, - }, - Spec: infrav1beta1.MongoDBUserSpec{ - Database: &infrav1beta1.DatabaseReference{ - Name: "does-not-exist", - }, - Credentials: &infrav1beta1.SecretReference{ - Name: "does-not-exist", - }, - }, - } - - Expect(k8sClient.Create(context.Background(), createdUser)).Should(Succeed()) - }) - - It("fails reconcile because there is no database", func() { - got := &infrav1beta1.MongoDBUser{} - Eventually(func() bool { - _ = k8sClient.Get(context.Background(), keyUser, got) - - return len(got.Status.Conditions) == 1 && - got.Status.Conditions[0].Reason == infrav1beta1.DatabaseNotFoundReason && - got.Status.Conditions[0].Status == "False" && - got.Status.Conditions[0].Type == infrav1beta1.UserReadyConditionType - }, timeout, interval).Should(BeTrue()) - }) - }) + for _, image := range []string{"mongo:4.4", "mongo:5"} { + var _ = Describe(image, func() { + var ( + container *mongodbContainer + err error + ) - Describe("fails if datatabase root secret not found", Ordered, func() { - var ( - createdDB *infrav1beta1.MongoDBDatabase - createdUser *infrav1beta1.MongoDBUser - keyUser types.NamespacedName - keyDB types.NamespacedName - ) - - namespace, _ := setupNamespace() - - It("creates database", func() { - keyDB = types.NamespacedName{ - Name: "mongodbdatabase-" + randStringRunes(5), - Namespace: namespace.Name, - } - createdDB = &infrav1beta1.MongoDBDatabase{ - ObjectMeta: metav1.ObjectMeta{ - Name: keyDB.Name, - Namespace: keyDB.Namespace, - }, - Spec: infrav1beta1.MongoDBDatabaseSpec{ - DatabaseSpec: &infrav1beta1.DatabaseSpec{ - RootSecret: &infrav1beta1.SecretReference{ - Name: "does-not-exist", + container, err = setupMongoDBContainer(context.TODO(), image) + Expect(err).NotTo(HaveOccurred(), "failed to start mongodb container") + + Describe("fails if database not found", Ordered, func() { + var ( + createdUser *infrav1beta1.MongoDBUser + keyUser types.NamespacedName + ) + + namespace, _ := setupNamespace() + + It("creates user", func() { + keyUser = types.NamespacedName{ + Name: "mongodbuser-" + randStringRunes(5), + Namespace: namespace.Name, + } + createdUser = &infrav1beta1.MongoDBUser{ + ObjectMeta: metav1.ObjectMeta{ + Name: keyUser.Name, + Namespace: keyUser.Namespace, }, - }, - }, - } - Expect(k8sClient.Create(context.Background(), createdDB)).Should(Succeed()) - }) - - It("adds user", func() { - keyUser = types.NamespacedName{ - Name: "mongodbuser-" + randStringRunes(5), - Namespace: namespace.Name, - } - createdUser = &infrav1beta1.MongoDBUser{ - ObjectMeta: metav1.ObjectMeta{ - Name: keyUser.Name, - Namespace: keyUser.Namespace, - }, - Spec: infrav1beta1.MongoDBUserSpec{ - Database: &infrav1beta1.DatabaseReference{ - Name: keyDB.Name, - }, - Credentials: &infrav1beta1.SecretReference{ - Name: "does-not-exist", - }, - }, - } - Expect(k8sClient.Create(context.Background(), createdUser)).Should(Succeed()) - }) - - It("fails reconcile because the db root secret is not found", func() { - got := &infrav1beta1.MongoDBUser{} - Eventually(func() bool { - _ = k8sClient.Get(context.Background(), keyUser, got) - - return len(got.Status.Conditions) == 1 && - got.Status.Conditions[0].Reason == infrav1beta1.CredentialsNotFoundReason && - got.Status.Conditions[0].Status == "False" && - got.Status.Conditions[0].Type == infrav1beta1.UserReadyConditionType - - }, timeout, interval).Should(BeTrue()) - }) - }) - - Describe("fails if datatabase root secret not found", Ordered, func() { - var ( - createdDB *infrav1beta1.MongoDBDatabase - createdUser *infrav1beta1.MongoDBUser - keyUser types.NamespacedName - keyDB types.NamespacedName - ) - - namespace, rootSecret := setupNamespace() - - It("adds database", func() { - keyDB = types.NamespacedName{ - Name: "mongodbdatabase-" + randStringRunes(5), - Namespace: namespace.Name, - } - createdDB = &infrav1beta1.MongoDBDatabase{ - ObjectMeta: metav1.ObjectMeta{ - Name: keyDB.Name, - Namespace: keyDB.Namespace, - }, - Spec: infrav1beta1.MongoDBDatabaseSpec{ - DatabaseSpec: &infrav1beta1.DatabaseSpec{ - Address: "mongodb://does-not-exist:27017", - RootSecret: &infrav1beta1.SecretReference{ - Name: rootSecret.Name, + Spec: infrav1beta1.MongoDBUserSpec{ + Database: &infrav1beta1.DatabaseReference{ + Name: "does-not-exist", + }, + Credentials: &infrav1beta1.SecretReference{ + Name: "does-not-exist", + }, }, - }, - }, - } - Expect(k8sClient.Create(context.Background(), createdDB)).Should(Succeed()) - }) - - It("adds user", func() { - keyUser = types.NamespacedName{ - Name: "mongodbuser-" + randStringRunes(5), - Namespace: namespace.Name, - } - createdUser = &infrav1beta1.MongoDBUser{ - ObjectMeta: metav1.ObjectMeta{ - Name: keyUser.Name, - Namespace: keyUser.Namespace, - }, - Spec: infrav1beta1.MongoDBUserSpec{ - Database: &infrav1beta1.DatabaseReference{ - Name: keyDB.Name, - }, - Credentials: &infrav1beta1.SecretReference{ - Name: "does-not-exist", - }, - }, - } - Expect(k8sClient.Create(context.Background(), createdUser)).Should(Succeed()) - }) + } - It("expecs reconcile to fail because database is not reachable", func() { - got := &infrav1beta1.MongoDBUser{} - Eventually(func() bool { - _ = k8sClient.Get(context.Background(), keyUser, got) - return len(got.Status.Conditions) == 1 && - got.Status.Conditions[0].Reason == infrav1beta1.ConnectionFailedReason && - got.Status.Conditions[0].Status == "False" && - got.Status.Conditions[0].Type == infrav1beta1.UserReadyConditionType - - }, timeout, interval).Should(BeTrue()) - }) - }) - - Describe("fails if user secret not found", Ordered, func() { - var ( - createdDB *infrav1beta1.MongoDBDatabase - createdUser *infrav1beta1.MongoDBUser - keyUser types.NamespacedName - keyDB types.NamespacedName - ) - - namespace, rootSecret := setupNamespace() - - It("adds database", func() { - keyDB = types.NamespacedName{ - Name: "mongodbdatabase-" + randStringRunes(5), - Namespace: namespace.Name, - } - createdDB = &infrav1beta1.MongoDBDatabase{ - ObjectMeta: metav1.ObjectMeta{ - Name: keyDB.Name, - Namespace: keyDB.Namespace, - }, - Spec: infrav1beta1.MongoDBDatabaseSpec{ - DatabaseSpec: &infrav1beta1.DatabaseSpec{ - Address: container.URI, - RootSecret: &infrav1beta1.SecretReference{ - Name: rootSecret.Name, - }, - }, - }, - } - Expect(k8sClient.Create(context.Background(), createdDB)).Should(Succeed()) - }) + Expect(k8sClient.Create(context.Background(), createdUser)).Should(Succeed()) + }) - It("adds user", func() { - keyUser = types.NamespacedName{ - Name: "mongodbuser-" + randStringRunes(5), - Namespace: namespace.Name, - } - createdUser = &infrav1beta1.MongoDBUser{ - ObjectMeta: metav1.ObjectMeta{ - Name: keyUser.Name, - Namespace: keyUser.Namespace, - }, - Spec: infrav1beta1.MongoDBUserSpec{ - Database: &infrav1beta1.DatabaseReference{ - Name: keyDB.Name, - }, - Credentials: &infrav1beta1.SecretReference{ - Name: "does-not-exist", - }, - }, - } - Expect(k8sClient.Create(context.Background(), createdUser)).Should(Succeed()) - }) + It("fails reconcile because there is no database", func() { + got := &infrav1beta1.MongoDBUser{} + Eventually(func() bool { + _ = k8sClient.Get(context.Background(), keyUser, got) - It("fails reconcile because user credentials are not found", func() { - got := &infrav1beta1.MongoDBUser{} - Eventually(func() bool { - _ = k8sClient.Get(context.Background(), keyUser, got) - return len(got.Status.Conditions) == 1 && - got.Status.Conditions[0].Reason == infrav1beta1.CredentialsNotFoundReason && - got.Status.Conditions[0].Status == "False" && - strings.Contains(got.Status.Conditions[0].Message, "referencing secret was not found:") && - got.Status.Conditions[0].Type == infrav1beta1.UserReadyConditionType - - }, timeout, interval).Should(BeTrue()) - }) - }) + return len(got.Status.Conditions) == 1 && + got.Status.Conditions[0].Reason == infrav1beta1.DatabaseNotFoundReason && + got.Status.Conditions[0].Status == "False" && + got.Status.Conditions[0].Type == infrav1beta1.UserReadyConditionType + }, timeout, interval).Should(BeTrue()) + }) + }) - Describe("fails if user secret exists but fields not found", Ordered, func() { - var ( - createdDB *infrav1beta1.MongoDBDatabase - createdUser *infrav1beta1.MongoDBUser - keyUser types.NamespacedName - keyDB types.NamespacedName - createdSecret *v1.Secret - keySecret types.NamespacedName - password string - ) - - namespace, rootSecret := setupNamespace() - - It("adds database", func() { - keyDB = types.NamespacedName{ - Name: "mongodbdatabase-" + randStringRunes(5), - Namespace: namespace.Name, - } - createdDB = &infrav1beta1.MongoDBDatabase{ - ObjectMeta: metav1.ObjectMeta{ - Name: keyDB.Name, - Namespace: keyDB.Namespace, - }, - Spec: infrav1beta1.MongoDBDatabaseSpec{ - DatabaseSpec: &infrav1beta1.DatabaseSpec{ - Address: container.URI, - RootSecret: &infrav1beta1.SecretReference{ - Name: rootSecret.Name, + Describe("fails if datatabase root secret not found", Ordered, func() { + var ( + createdDB *infrav1beta1.MongoDBDatabase + createdUser *infrav1beta1.MongoDBUser + keyUser types.NamespacedName + keyDB types.NamespacedName + ) + + namespace, _ := setupNamespace() + + It("creates database", func() { + keyDB = types.NamespacedName{ + Name: "mongodbdatabase-" + randStringRunes(5), + Namespace: namespace.Name, + } + createdDB = &infrav1beta1.MongoDBDatabase{ + ObjectMeta: metav1.ObjectMeta{ + Name: keyDB.Name, + Namespace: keyDB.Namespace, }, - }, - }, - } - Expect(k8sClient.Create(context.Background(), createdDB)).Should(Succeed()) - }) - - It("adds user", func() { - keySecret = types.NamespacedName{ - Name: "secret-" + randStringRunes(5), - Namespace: namespace.Name, - } - keyUser = types.NamespacedName{ - Name: "mongodbuser-" + randStringRunes(5), - Namespace: namespace.Name, - } - createdUser = &infrav1beta1.MongoDBUser{ - ObjectMeta: metav1.ObjectMeta{ - Name: keyUser.Name, - Namespace: keyUser.Namespace, - }, - Spec: infrav1beta1.MongoDBUserSpec{ - Database: &infrav1beta1.DatabaseReference{ - Name: keyDB.Name, - }, - Credentials: &infrav1beta1.SecretReference{ - Name: keySecret.Name, - UserField: "does-not-exist", - }, - }, - } - Expect(k8sClient.Create(context.Background(), createdUser)).Should(Succeed()) - }) - - It("adds secret", func() { - password = randStringRunes(5) - createdSecret = &v1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: keySecret.Name, - Namespace: keySecret.Namespace, - }, - Data: map[string][]byte{ - "username": []byte(keyUser.Name), - "password": []byte(password), - }, - } - Expect(k8sClient.Create(context.Background(), createdSecret)).Should(Succeed()) - }) - - It("fails reconcile because user field in secret is not found", func() { - got := &infrav1beta1.MongoDBUser{} - Eventually(func() bool { - _ = k8sClient.Get(context.Background(), keyUser, got) - return len(got.Status.Conditions) == 1 && - got.Status.Conditions[0].Reason == infrav1beta1.CredentialsNotFoundReason && - got.Status.Conditions[0].Status == "False" && - strings.Contains(got.Status.Conditions[0].Message, "credentials field not found in referenced secret:") && - got.Status.Conditions[0].Type == infrav1beta1.UserReadyConditionType - - }, timeout, interval).Should(BeTrue()) - }) - }) - - Describe("Successful user creation", Ordered, func() { - var ( - createdDB *infrav1beta1.MongoDBDatabase - createdUser *infrav1beta1.MongoDBUser - createdSecret *v1.Secret - keyUser types.NamespacedName - keyDB types.NamespacedName - keySecret types.NamespacedName - password string - ) - - namespace, rootSecret := setupNamespace() - - Describe("creates readWrite user if it does not exists", Ordered, func() { - var ( - client *mongo.Client - ) - - It("adds database", func() { - keyDB = types.NamespacedName{ - Name: "mongodbdatabase-" + randStringRunes(5), - Namespace: namespace.Name, - } - createdDB = &infrav1beta1.MongoDBDatabase{ - ObjectMeta: metav1.ObjectMeta{ - Name: keyDB.Name, - Namespace: keyDB.Namespace, - }, - Spec: infrav1beta1.MongoDBDatabaseSpec{ - DatabaseSpec: &infrav1beta1.DatabaseSpec{ - Address: container.URI, - RootSecret: &infrav1beta1.SecretReference{ - Name: rootSecret.Name, + Spec: infrav1beta1.MongoDBDatabaseSpec{ + DatabaseSpec: &infrav1beta1.DatabaseSpec{ + RootSecret: &infrav1beta1.SecretReference{ + Name: "does-not-exist", + }, }, }, - }, - } - - Expect(k8sClient.Create(context.Background(), createdDB)).Should(Succeed()) - }) + } + Expect(k8sClient.Create(context.Background(), createdDB)).Should(Succeed()) + }) - It("adds user", func() { - keySecret = types.NamespacedName{ - Name: "secret-" + randStringRunes(5), - Namespace: namespace.Name, - } - keyUser = types.NamespacedName{ - Name: "mongodbuser-" + randStringRunes(5), - Namespace: namespace.Name, - } - createdUser = &infrav1beta1.MongoDBUser{ - ObjectMeta: metav1.ObjectMeta{ - Name: keyUser.Name, - Namespace: keyUser.Namespace, - }, - Spec: infrav1beta1.MongoDBUserSpec{ - Database: &infrav1beta1.DatabaseReference{ - Name: keyDB.Name, - }, - Credentials: &infrav1beta1.SecretReference{ - Name: keySecret.Name, + It("adds user", func() { + keyUser = types.NamespacedName{ + Name: "mongodbuser-" + randStringRunes(5), + Namespace: namespace.Name, + } + createdUser = &infrav1beta1.MongoDBUser{ + ObjectMeta: metav1.ObjectMeta{ + Name: keyUser.Name, + Namespace: keyUser.Namespace, }, - Roles: &[]infrav1beta1.MongoDBUserRole{ - infrav1beta1.MongoDBUserRole{ - Name: "readWrite", - DB: "foo", + Spec: infrav1beta1.MongoDBUserSpec{ + Database: &infrav1beta1.DatabaseReference{ + Name: keyDB.Name, + }, + Credentials: &infrav1beta1.SecretReference{ + Name: "does-not-exist", }, }, - }, - } - Expect(k8sClient.Create(context.Background(), createdUser)).Should(Succeed()) - }) - - It("adds secret", func() { - password = randStringRunes(5) - createdSecret = &v1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: keySecret.Name, - Namespace: keySecret.Namespace, - }, - Data: map[string][]byte{ - "username": []byte(keyUser.Name), - "password": []byte(password), - }, - } - Expect(k8sClient.Create(context.Background(), createdSecret)).Should(Succeed()) - }) - - It("expects ready user", func() { - got := &infrav1beta1.MongoDBUser{} - Eventually(func() bool { - _ = k8sClient.Get(context.Background(), keyUser, got) - return len(got.Status.Conditions) == 1 && - got.Status.Conditions[0].Reason == infrav1beta1.UserProvisioningSuccessfulReason && - got.Status.Conditions[0].Status == "True" && - got.Status.Conditions[0].Type == infrav1beta1.UserReadyConditionType - - }, timeout, interval).Should(BeTrue()) - }) - - It("can access the created database", func() { - o := options.Client() - o.SetConnectTimeout(time.Duration(1) * time.Second) - o.SetServerSelectionTimeout(time.Duration(1) * time.Second) - o.ApplyURI(container.URI) - o.SetAuth(options.Credential{ - AuthSource: createdDB.ObjectMeta.Name, - Username: keyUser.Name, - Password: password, + } + Expect(k8sClient.Create(context.Background(), createdUser)).Should(Succeed()) }) - client, err = mongo.Connect(ctx, o) - Expect(err).NotTo(HaveOccurred(), "failed to connecto to mongodb") - - Eventually(func() error { - return client.Ping(ctx, readpref.Primary()) - }, timeout, interval).Should(Succeed()) - }) - - It("has write access to the referenced role database", func() { - _, err = client.Database("foo").Collection(randStringRunes(5)).InsertOne(ctx, bson.D{}) - Expect(err).NotTo(HaveOccurred(), "failed to insert doc") - }) + It("fails reconcile because the db root secret is not found", func() { + got := &infrav1beta1.MongoDBUser{} + Eventually(func() bool { + _ = k8sClient.Get(context.Background(), keyUser, got) - It("has has no access to another database", func() { - _, err = client.Database("bar").Collection(randStringRunes(5)).InsertOne(ctx, bson.D{}) - Expect(err).To(HaveOccurred(), "failed to insert doc") - }) + return len(got.Status.Conditions) == 1 && + got.Status.Conditions[0].Reason == infrav1beta1.CredentialsNotFoundReason && + got.Status.Conditions[0].Status == "False" && + got.Status.Conditions[0].Type == infrav1beta1.UserReadyConditionType - It("can't access the created database with invalid credentials", func() { - o := options.Client() - o.SetConnectTimeout(time.Duration(1) * time.Second) - o.SetServerSelectionTimeout(time.Duration(1) * time.Second) - o.ApplyURI(container.URI) - o.SetAuth(options.Credential{ - AuthSource: createdDB.ObjectMeta.Name, - Username: keyUser.Name, - Password: "invalid", + }, timeout, interval).Should(BeTrue()) }) - - client, err = mongo.Connect(ctx, o) - Expect(err).NotTo(HaveOccurred(), "failed to connecto to mongodb") - - Eventually(func() error { - return client.Ping(ctx, readpref.Primary()) - }, timeout, interval).ShouldNot(Succeed()) - }) - }) - - Describe("Change password for user", Ordered, func() { - It("changes password in referenced user secret", func() { - password = randStringRunes(5) - createdSecret.Data = map[string][]byte{ - "username": []byte(createdUser.ObjectMeta.Name), - "password": []byte(password), - } - Expect(k8sClient.Update(context.Background(), createdSecret)).Should(Succeed()) - }) - - It("expects ready user", func() { - got := &infrav1beta1.MongoDBUser{} - Eventually(func() bool { - _ = k8sClient.Get(context.Background(), keyUser, got) - return len(got.Status.Conditions) == 1 && - got.Status.Conditions[0].Reason == infrav1beta1.UserProvisioningSuccessfulReason && - got.Status.Conditions[0].Status == "True" && - got.Status.Conditions[0].Type == infrav1beta1.UserReadyConditionType - - }, timeout, interval).Should(BeTrue()) }) - It("can access the database with the new password", func() { - o := options.Client() - o.SetConnectTimeout(time.Duration(1) * time.Second) - o.SetServerSelectionTimeout(time.Duration(1) * time.Second) - o.ApplyURI(container.URI) - - o.SetAuth(options.Credential{ - AuthSource: createdDB.ObjectMeta.Name, - Username: createdUser.ObjectMeta.Name, - Password: password, + Describe("fails if datatabase root secret not found", Ordered, func() { + var ( + createdDB *infrav1beta1.MongoDBDatabase + createdUser *infrav1beta1.MongoDBUser + keyUser types.NamespacedName + keyDB types.NamespacedName + ) + + namespace, rootSecret := setupNamespace() + + It("adds database", func() { + keyDB = types.NamespacedName{ + Name: "mongodbdatabase-" + randStringRunes(5), + Namespace: namespace.Name, + } + createdDB = &infrav1beta1.MongoDBDatabase{ + ObjectMeta: metav1.ObjectMeta{ + Name: keyDB.Name, + Namespace: keyDB.Namespace, + }, + Spec: infrav1beta1.MongoDBDatabaseSpec{ + DatabaseSpec: &infrav1beta1.DatabaseSpec{ + Address: "mongodb://does-not-exist:27017", + RootSecret: &infrav1beta1.SecretReference{ + Name: rootSecret.Name, + }, + }, + }, + } + Expect(k8sClient.Create(context.Background(), createdDB)).Should(Succeed()) }) - client, err := mongo.Connect(ctx, o) - Expect(err).NotTo(HaveOccurred(), "failed to connecto to mongodb") - - Eventually(func() error { - return client.Ping(ctx, readpref.Primary()) - }, timeout, interval).Should(Succeed()) - }) - }) - - Describe("Change role", Ordered, func() { - var ( - client *mongo.Client - ) - - It("changes role to readOnly", func() { - err := k8sClient.Get(context.Background(), keyUser, createdUser) - Expect(err).Should(Succeed()) - - createdUser.Spec.Roles = &[]infrav1beta1.MongoDBUserRole{ - infrav1beta1.MongoDBUserRole{ - Name: "read", - DB: "foo", - }, - } + It("adds user", func() { + keyUser = types.NamespacedName{ + Name: "mongodbuser-" + randStringRunes(5), + Namespace: namespace.Name, + } + createdUser = &infrav1beta1.MongoDBUser{ + ObjectMeta: metav1.ObjectMeta{ + Name: keyUser.Name, + Namespace: keyUser.Namespace, + }, + Spec: infrav1beta1.MongoDBUserSpec{ + Database: &infrav1beta1.DatabaseReference{ + Name: keyDB.Name, + }, + Credentials: &infrav1beta1.SecretReference{ + Name: "does-not-exist", + }, + }, + } + Expect(k8sClient.Create(context.Background(), createdUser)).Should(Succeed()) + }) - Expect(k8sClient.Update(context.Background(), createdUser)).Should(Succeed()) + It("expects reconcile to fail because database is not reachable", func() { + got := &infrav1beta1.MongoDBUser{} + Eventually(func() bool { + fmt.Printf("%#v\n\n\n", got) + _ = k8sClient.Get(context.Background(), keyUser, got) + return len(got.Status.Conditions) == 1 && + got.Status.Conditions[0].Reason == infrav1beta1.ConnectionFailedReason && + got.Status.Conditions[0].Status == "False" && + got.Status.Conditions[0].Type == infrav1beta1.UserReadyConditionType + + }, timeout, interval).Should(BeTrue()) + }) }) - It("expects ready user", func() { - got := &infrav1beta1.MongoDBUser{} - Eventually(func() bool { - _ = k8sClient.Get(context.Background(), keyUser, got) + Describe("fails if user secret not found", Ordered, func() { + var ( + createdDB *infrav1beta1.MongoDBDatabase + createdUser *infrav1beta1.MongoDBUser + keyUser types.NamespacedName + keyDB types.NamespacedName + ) + + namespace, rootSecret := setupNamespace() + + It("adds database", func() { + keyDB = types.NamespacedName{ + Name: "mongodbdatabase-" + randStringRunes(5), + Namespace: namespace.Name, + } + createdDB = &infrav1beta1.MongoDBDatabase{ + ObjectMeta: metav1.ObjectMeta{ + Name: keyDB.Name, + Namespace: keyDB.Namespace, + }, + Spec: infrav1beta1.MongoDBDatabaseSpec{ + DatabaseSpec: &infrav1beta1.DatabaseSpec{ + Address: container.URI, + RootSecret: &infrav1beta1.SecretReference{ + Name: rootSecret.Name, + }, + }, + }, + } + Expect(k8sClient.Create(context.Background(), createdDB)).Should(Succeed()) + }) - return len(got.Status.Conditions) == 1 && - got.Status.Conditions[0].Reason == infrav1beta1.UserProvisioningSuccessfulReason && - got.Status.Conditions[0].Status == "True" && - got.Status.Conditions[0].Type == infrav1beta1.UserReadyConditionType && - got.ObjectMeta.Generation == got.Status.ObservedGeneration + It("adds user", func() { + keyUser = types.NamespacedName{ + Name: "mongodbuser-" + randStringRunes(5), + Namespace: namespace.Name, + } + createdUser = &infrav1beta1.MongoDBUser{ + ObjectMeta: metav1.ObjectMeta{ + Name: keyUser.Name, + Namespace: keyUser.Namespace, + }, + Spec: infrav1beta1.MongoDBUserSpec{ + Database: &infrav1beta1.DatabaseReference{ + Name: keyDB.Name, + }, + Credentials: &infrav1beta1.SecretReference{ + Name: "does-not-exist", + }, + }, + } + Expect(k8sClient.Create(context.Background(), createdUser)).Should(Succeed()) + }) - }, timeout, interval).Should(BeTrue()) + It("fails reconcile because user credentials are not found", func() { + got := &infrav1beta1.MongoDBUser{} + Eventually(func() bool { + _ = k8sClient.Get(context.Background(), keyUser, got) + return len(got.Status.Conditions) == 1 && + got.Status.Conditions[0].Reason == infrav1beta1.CredentialsNotFoundReason && + got.Status.Conditions[0].Status == "False" && + strings.Contains(got.Status.Conditions[0].Message, "referencing secret was not found:") && + got.Status.Conditions[0].Type == infrav1beta1.UserReadyConditionType + + }, timeout, interval).Should(BeTrue()) + }) }) - It("can't insert doc", func() { - o := options.Client() - o.SetConnectTimeout(time.Duration(1) * time.Second) - o.SetServerSelectionTimeout(time.Duration(1) * time.Second) - o.ApplyURI(container.URI) - - o.SetAuth(options.Credential{ - AuthSource: createdDB.ObjectMeta.Name, - Username: createdUser.ObjectMeta.Name, - Password: password, + Describe("fails if user secret exists but fields not found", Ordered, func() { + var ( + createdDB *infrav1beta1.MongoDBDatabase + createdUser *infrav1beta1.MongoDBUser + keyUser types.NamespacedName + keyDB types.NamespacedName + createdSecret *v1.Secret + keySecret types.NamespacedName + password string + ) + + namespace, rootSecret := setupNamespace() + + It("adds database", func() { + keyDB = types.NamespacedName{ + Name: "mongodbdatabase-" + randStringRunes(5), + Namespace: namespace.Name, + } + createdDB = &infrav1beta1.MongoDBDatabase{ + ObjectMeta: metav1.ObjectMeta{ + Name: keyDB.Name, + Namespace: keyDB.Namespace, + }, + Spec: infrav1beta1.MongoDBDatabaseSpec{ + DatabaseSpec: &infrav1beta1.DatabaseSpec{ + Address: container.URI, + RootSecret: &infrav1beta1.SecretReference{ + Name: rootSecret.Name, + }, + }, + }, + } + Expect(k8sClient.Create(context.Background(), createdDB)).Should(Succeed()) }) - client, err = mongo.Connect(ctx, o) - Expect(err).NotTo(HaveOccurred(), "failed to connecto to mongodb") + It("adds user", func() { + keySecret = types.NamespacedName{ + Name: "secret-" + randStringRunes(5), + Namespace: namespace.Name, + } + keyUser = types.NamespacedName{ + Name: "mongodbuser-" + randStringRunes(5), + Namespace: namespace.Name, + } + createdUser = &infrav1beta1.MongoDBUser{ + ObjectMeta: metav1.ObjectMeta{ + Name: keyUser.Name, + Namespace: keyUser.Namespace, + }, + Spec: infrav1beta1.MongoDBUserSpec{ + Database: &infrav1beta1.DatabaseReference{ + Name: keyDB.Name, + }, + Credentials: &infrav1beta1.SecretReference{ + Name: keySecret.Name, + UserField: "does-not-exist", + }, + }, + } + Expect(k8sClient.Create(context.Background(), createdUser)).Should(Succeed()) + }) - Eventually(func() error { - _, err = client.Database("foo").Collection(randStringRunes(5)).InsertOne(ctx, bson.D{}) - return err - }, timeout, interval).ShouldNot(Succeed()) - }) + It("adds secret", func() { + password = randStringRunes(5) + createdSecret = &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: keySecret.Name, + Namespace: keySecret.Namespace, + }, + Data: map[string][]byte{ + "username": []byte(keyUser.Name), + "password": []byte(password), + }, + } + Expect(k8sClient.Create(context.Background(), createdSecret)).Should(Succeed()) + }) - It("can still read though", func() { - Eventually(func() error { - _, err := client.Database("foo").Collection(randStringRunes(5)).Find(ctx, bson.D{}) - return err - }, timeout, interval).Should(Succeed()) + It("fails reconcile because user field in secret is not found", func() { + got := &infrav1beta1.MongoDBUser{} + Eventually(func() bool { + _ = k8sClient.Get(context.Background(), keyUser, got) + return len(got.Status.Conditions) == 1 && + got.Status.Conditions[0].Reason == infrav1beta1.CredentialsNotFoundReason && + got.Status.Conditions[0].Status == "False" && + strings.Contains(got.Status.Conditions[0].Message, "credentials field not found in referenced secret:") && + got.Status.Conditions[0].Type == infrav1beta1.UserReadyConditionType + + }, timeout, interval).Should(BeTrue()) + }) }) - }) - Describe("Delete user removes user from mongodb", Ordered, func() { - var ( - client *mongo.Client - ) + Describe("Successful user creation", Ordered, func() { + var ( + createdDB *infrav1beta1.MongoDBDatabase + createdUser *infrav1beta1.MongoDBUser + createdSecret *v1.Secret + keyUser types.NamespacedName + keyDB types.NamespacedName + keySecret types.NamespacedName + password string + ) + + namespace, rootSecret := setupNamespace() + + Describe("creates readWrite user if it does not exists", Ordered, func() { + var ( + client *mongo.Client + ) + + It("adds database", func() { + keyDB = types.NamespacedName{ + Name: "mongodbdatabase-" + randStringRunes(5), + Namespace: namespace.Name, + } + createdDB = &infrav1beta1.MongoDBDatabase{ + ObjectMeta: metav1.ObjectMeta{ + Name: keyDB.Name, + Namespace: keyDB.Namespace, + }, + Spec: infrav1beta1.MongoDBDatabaseSpec{ + DatabaseSpec: &infrav1beta1.DatabaseSpec{ + Address: container.URI, + RootSecret: &infrav1beta1.SecretReference{ + Name: rootSecret.Name, + }, + }, + }, + } + + Expect(k8sClient.Create(context.Background(), createdDB)).Should(Succeed()) + }) + + It("adds user", func() { + keySecret = types.NamespacedName{ + Name: "secret-" + randStringRunes(5), + Namespace: namespace.Name, + } + keyUser = types.NamespacedName{ + Name: "mongodbuser-" + randStringRunes(5), + Namespace: namespace.Name, + } + createdUser = &infrav1beta1.MongoDBUser{ + ObjectMeta: metav1.ObjectMeta{ + Name: keyUser.Name, + Namespace: keyUser.Namespace, + }, + Spec: infrav1beta1.MongoDBUserSpec{ + Database: &infrav1beta1.DatabaseReference{ + Name: keyDB.Name, + }, + Credentials: &infrav1beta1.SecretReference{ + Name: keySecret.Name, + }, + Roles: &[]infrav1beta1.MongoDBUserRole{ + infrav1beta1.MongoDBUserRole{ + Name: "readWrite", + DB: "foo", + }, + }, + }, + } + Expect(k8sClient.Create(context.Background(), createdUser)).Should(Succeed()) + }) + + It("adds secret", func() { + password = randStringRunes(5) + createdSecret = &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: keySecret.Name, + Namespace: keySecret.Namespace, + }, + Data: map[string][]byte{ + "username": []byte(keyUser.Name), + "password": []byte(password), + }, + } + Expect(k8sClient.Create(context.Background(), createdSecret)).Should(Succeed()) + }) + + It("expects ready user", func() { + got := &infrav1beta1.MongoDBUser{} + Eventually(func() bool { + _ = k8sClient.Get(context.Background(), keyUser, got) + return len(got.Status.Conditions) == 1 && + got.Status.Conditions[0].Reason == infrav1beta1.UserProvisioningSuccessfulReason && + got.Status.Conditions[0].Status == "True" && + got.Status.Conditions[0].Type == infrav1beta1.UserReadyConditionType + + }, timeout, interval).Should(BeTrue()) + }) + + It("can access the created database", func() { + o := options.Client() + o.SetConnectTimeout(time.Duration(1) * time.Second) + o.SetServerSelectionTimeout(time.Duration(1) * time.Second) + o.ApplyURI(container.URI) + o.SetAuth(options.Credential{ + AuthSource: createdDB.ObjectMeta.Name, + Username: keyUser.Name, + Password: password, + }) + + client, err = mongo.Connect(ctx, o) + Expect(err).NotTo(HaveOccurred(), "failed to connecto to mongodb") + + Eventually(func() error { + return client.Ping(ctx, readpref.Primary()) + }, timeout, interval).Should(Succeed()) + }) + + It("has write access to the referenced role database", func() { + _, err = client.Database("foo").Collection(randStringRunes(5)).InsertOne(ctx, bson.D{}) + Expect(err).NotTo(HaveOccurred(), "failed to insert doc") + }) + + It("has has no access to another database", func() { + _, err = client.Database("bar").Collection(randStringRunes(5)).InsertOne(ctx, bson.D{}) + Expect(err).To(HaveOccurred(), "failed to insert doc") + }) + + It("can't access the created database with invalid credentials", func() { + o := options.Client() + o.SetConnectTimeout(time.Duration(1) * time.Second) + o.SetServerSelectionTimeout(time.Duration(1) * time.Second) + o.ApplyURI(container.URI) + o.SetAuth(options.Credential{ + AuthSource: createdDB.ObjectMeta.Name, + Username: keyUser.Name, + Password: "invalid", + }) + + client, err = mongo.Connect(ctx, o) + Expect(err).NotTo(HaveOccurred(), "failed to connecto to mongodb") + + Eventually(func() error { + return client.Ping(ctx, readpref.Primary()) + }, timeout, interval).ShouldNot(Succeed()) + }) + }) - It("deletes user", func() { - Expect(k8sClient.Delete(context.Background(), createdUser)).Should(Succeed()) - }) + Describe("Change password for user", Ordered, func() { + It("changes password in referenced user secret", func() { + password = randStringRunes(5) + createdSecret.Data = map[string][]byte{ + "username": []byte(createdUser.ObjectMeta.Name), + "password": []byte(password), + } + Expect(k8sClient.Update(context.Background(), createdSecret)).Should(Succeed()) + }) + + It("expects ready user", func() { + got := &infrav1beta1.MongoDBUser{} + Eventually(func() bool { + _ = k8sClient.Get(context.Background(), keyUser, got) + return len(got.Status.Conditions) == 1 && + got.Status.Conditions[0].Reason == infrav1beta1.UserProvisioningSuccessfulReason && + got.Status.Conditions[0].Status == "True" && + got.Status.Conditions[0].Type == infrav1beta1.UserReadyConditionType + + }, timeout, interval).Should(BeTrue()) + }) + + It("can access the database with the new password", func() { + o := options.Client() + o.SetConnectTimeout(time.Duration(1) * time.Second) + o.SetServerSelectionTimeout(time.Duration(1) * time.Second) + o.ApplyURI(container.URI) + + o.SetAuth(options.Credential{ + AuthSource: createdDB.ObjectMeta.Name, + Username: createdUser.ObjectMeta.Name, + Password: password, + }) + + client, err := mongo.Connect(ctx, o) + Expect(err).NotTo(HaveOccurred(), "failed to connecto to mongodb") + + Eventually(func() error { + return client.Ping(ctx, readpref.Primary()) + }, timeout, interval).Should(Succeed()) + }) + }) - It("expects gone", func() { - got := &infrav1beta1.MongoDBUser{} - Eventually(func() error { - return k8sClient.Get(context.Background(), keyUser, got) - }, timeout, interval).ShouldNot(Succeed()) - }) + Describe("Change role", Ordered, func() { + var ( + client *mongo.Client + ) - It("can't authenticate anymore since the user is deleted", func() { - o := options.Client() - o.SetConnectTimeout(time.Duration(1) * time.Second) - o.SetServerSelectionTimeout(time.Duration(1) * time.Second) - o.ApplyURI(container.URI) + It("changes role to readOnly", func() { + err := k8sClient.Get(context.Background(), keyUser, createdUser) + Expect(err).Should(Succeed()) - o.SetAuth(options.Credential{ - AuthSource: createdDB.ObjectMeta.Name, - Username: createdUser.ObjectMeta.Name, - Password: password, + createdUser.Spec.Roles = &[]infrav1beta1.MongoDBUserRole{ + infrav1beta1.MongoDBUserRole{ + Name: "read", + DB: "foo", + }, + } + + Expect(k8sClient.Update(context.Background(), createdUser)).Should(Succeed()) + }) + + It("expects ready user", func() { + got := &infrav1beta1.MongoDBUser{} + Eventually(func() bool { + _ = k8sClient.Get(context.Background(), keyUser, got) + + return len(got.Status.Conditions) == 1 && + got.Status.Conditions[0].Reason == infrav1beta1.UserProvisioningSuccessfulReason && + got.Status.Conditions[0].Status == "True" && + got.Status.Conditions[0].Type == infrav1beta1.UserReadyConditionType && + got.ObjectMeta.Generation == got.Status.ObservedGeneration + + }, timeout, interval).Should(BeTrue()) + }) + + It("can't insert doc", func() { + o := options.Client() + o.SetConnectTimeout(time.Duration(1) * time.Second) + o.SetServerSelectionTimeout(time.Duration(1) * time.Second) + o.ApplyURI(container.URI) + + o.SetAuth(options.Credential{ + AuthSource: createdDB.ObjectMeta.Name, + Username: createdUser.ObjectMeta.Name, + Password: password, + }) + + client, err = mongo.Connect(ctx, o) + Expect(err).NotTo(HaveOccurred(), "failed to connecto to mongodb") + + Eventually(func() error { + _, err = client.Database("foo").Collection(randStringRunes(5)).InsertOne(ctx, bson.D{}) + return err + }, timeout, interval).ShouldNot(Succeed()) + }) + + It("can still read though", func() { + Eventually(func() error { + _, err := client.Database("foo").Collection(randStringRunes(5)).Find(ctx, bson.D{}) + return err + }, timeout, interval).Should(Succeed()) + }) }) - client, err = mongo.Connect(ctx, o) - Expect(err).NotTo(HaveOccurred(), "failed to connecto to mongodb") - - Eventually(func() error { - return client.Ping(ctx, readpref.Primary()) - }, timeout, interval).ShouldNot(Succeed()) + Describe("Delete user removes user from mongodb", Ordered, func() { + var ( + client *mongo.Client + ) + + It("deletes user", func() { + Expect(k8sClient.Delete(context.Background(), createdUser)).Should(Succeed()) + }) + + It("expects gone", func() { + got := &infrav1beta1.MongoDBUser{} + Eventually(func() error { + return k8sClient.Get(context.Background(), keyUser, got) + }, timeout, interval).ShouldNot(Succeed()) + }) + + It("can't authenticate anymore since the user is deleted", func() { + o := options.Client() + o.SetConnectTimeout(time.Duration(1) * time.Second) + o.SetServerSelectionTimeout(time.Duration(1) * time.Second) + o.ApplyURI(container.URI) + + o.SetAuth(options.Credential{ + AuthSource: createdDB.ObjectMeta.Name, + Username: createdUser.ObjectMeta.Name, + Password: password, + }) + + client, err = mongo.Connect(ctx, o) + Expect(err).NotTo(HaveOccurred(), "failed to connecto to mongodb") + + Eventually(func() error { + return client.Ping(ctx, readpref.Primary()) + }, timeout, interval).ShouldNot(Succeed()) + }) + }) }) }) - }) + } }) diff --git a/controllers/postgresql_test.go b/controllers/postgresql_test.go index c20dcc4..e06ecc9 100644 --- a/controllers/postgresql_test.go +++ b/controllers/postgresql_test.go @@ -29,7 +29,6 @@ import ( "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" corev1 "k8s.io/api/core/v1" - v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -42,9 +41,9 @@ type postgresqlContainer struct { URI string } -func setupPostgreSQLContainer(ctx context.Context) (*postgresqlContainer, error) { +func setupPostgreSQLContainer(ctx context.Context, image string) (*postgresqlContainer, error) { req := testcontainers.ContainerRequest{ - Image: "postgres:12", + Image: image, ExposedPorts: []string{"5432/tcp"}, WaitingFor: wait.ForListeningPort("5432"), Env: map[string]string{ @@ -61,17 +60,12 @@ func setupPostgreSQLContainer(ctx context.Context) (*postgresqlContainer, error) return nil, err } - ip, err := container.Host(ctx) + ip, err := container.ContainerIP(ctx) if err != nil { return nil, err } - mappedPort, err := container.MappedPort(ctx, "5432") - if err != nil { - return nil, err - } - - uri := fmt.Sprintf("postgresql://%s:%s", ip, mappedPort.Port()) + uri := fmt.Sprintf("postgresql://%s:5432", ip) return &postgresqlContainer{Container: container, URI: uri}, nil } @@ -82,575 +76,579 @@ var _ = Describe("PostgreSQL", func() { interval = time.Second * 1 ) - var ( - container *postgresqlContainer - err error - ) - - container, err = setupPostgreSQLContainer(context.TODO()) - Expect(err).NotTo(HaveOccurred(), "failed to start postgres container") - - Describe("fails if database not found", Ordered, func() { - var ( - createdUser *infrav1beta1.PostgreSQLUser - keyUser types.NamespacedName - ) - - namespace, _ := setupNamespace() - - It("creates user", func() { - keyUser = types.NamespacedName{ - Name: "postgresuser-" + randStringRunes(5), - Namespace: namespace.Name, - } - createdUser = &infrav1beta1.PostgreSQLUser{ - ObjectMeta: metav1.ObjectMeta{ - Name: keyUser.Name, - Namespace: keyUser.Namespace, - }, - Spec: infrav1beta1.PostgreSQLUserSpec{ - Database: &infrav1beta1.DatabaseReference{ - Name: "does-not-exist", - }, - Credentials: &infrav1beta1.SecretReference{ - Name: "does-not-exist", - }, - }, - } - - Expect(k8sClient.Create(context.Background(), createdUser)).Should(Succeed()) - }) - - It("fails reconcile because there is no database", func() { - got := &infrav1beta1.PostgreSQLUser{} - Eventually(func() bool { - _ = k8sClient.Get(context.Background(), keyUser, got) - - return len(got.Status.Conditions) == 1 && - got.Status.Conditions[0].Reason == infrav1beta1.DatabaseNotFoundReason && - got.Status.Conditions[0].Status == "False" && - got.Status.Conditions[0].Type == infrav1beta1.UserReadyConditionType - }, timeout, interval).Should(BeTrue()) - }) - }) + for _, image := range []string{"postgres:12", "postgres:13", "postgres:14", "postgres:15"} { + var _ = Describe(image, func() { + var ( + container *postgresqlContainer + err error + ) - Describe("fails if datatabase root secret not found", Ordered, func() { - var ( - createdDB *infrav1beta1.PostgreSQLDatabase - createdUser *infrav1beta1.PostgreSQLUser - keyUser types.NamespacedName - keyDB types.NamespacedName - ) - - namespace, _ := setupNamespace() - - It("creates database", func() { - keyDB = types.NamespacedName{ - Name: "postgresdatabase-" + randStringRunes(5), - Namespace: namespace.Name, - } - createdDB = &infrav1beta1.PostgreSQLDatabase{ - ObjectMeta: metav1.ObjectMeta{ - Name: keyDB.Name, - Namespace: keyDB.Namespace, - }, - Spec: infrav1beta1.PostgreSQLDatabaseSpec{ - DatabaseSpec: &infrav1beta1.DatabaseSpec{ - RootSecret: &infrav1beta1.SecretReference{ - Name: "does-not-exist", + container, err = setupPostgreSQLContainer(context.TODO(), image) + Expect(err).NotTo(HaveOccurred(), "failed to start postgres container") + + Describe("fails if database not found", Ordered, func() { + var ( + createdUser *infrav1beta1.PostgreSQLUser + keyUser types.NamespacedName + ) + + namespace, _ := setupNamespace() + + It("creates user", func() { + keyUser = types.NamespacedName{ + Name: "postgresuser-" + randStringRunes(5), + Namespace: namespace.Name, + } + createdUser = &infrav1beta1.PostgreSQLUser{ + ObjectMeta: metav1.ObjectMeta{ + Name: keyUser.Name, + Namespace: keyUser.Namespace, }, - }, - }, - } - Expect(k8sClient.Create(context.Background(), createdDB)).Should(Succeed()) - }) - - It("adds user", func() { - keyUser = types.NamespacedName{ - Name: "postgresuser-" + randStringRunes(5), - Namespace: namespace.Name, - } - createdUser = &infrav1beta1.PostgreSQLUser{ - ObjectMeta: metav1.ObjectMeta{ - Name: keyUser.Name, - Namespace: keyUser.Namespace, - }, - Spec: infrav1beta1.PostgreSQLUserSpec{ - Database: &infrav1beta1.DatabaseReference{ - Name: keyDB.Name, - }, - Credentials: &infrav1beta1.SecretReference{ - Name: "does-not-exist", - }, - }, - } - Expect(k8sClient.Create(context.Background(), createdUser)).Should(Succeed()) - }) - - It("fails reconcile because the db root secret is not found", func() { - got := &infrav1beta1.PostgreSQLUser{} - Eventually(func() bool { - _ = k8sClient.Get(context.Background(), keyUser, got) - - return len(got.Status.Conditions) == 1 && - got.Status.Conditions[0].Reason == infrav1beta1.CredentialsNotFoundReason && - got.Status.Conditions[0].Status == "False" && - got.Status.Conditions[0].Type == infrav1beta1.UserReadyConditionType - - }, timeout, interval).Should(BeTrue()) - }) - }) - - Describe("fails if datatabase root secret not found", Ordered, func() { - var ( - createdDB *infrav1beta1.PostgreSQLDatabase - createdUser *infrav1beta1.PostgreSQLUser - keyUser types.NamespacedName - keyDB types.NamespacedName - ) - - namespace, rootSecret := setupNamespace() - - It("adds database", func() { - keyDB = types.NamespacedName{ - Name: "postgresdatabase-" + randStringRunes(5), - Namespace: namespace.Name, - } - createdDB = &infrav1beta1.PostgreSQLDatabase{ - ObjectMeta: metav1.ObjectMeta{ - Name: keyDB.Name, - Namespace: keyDB.Namespace, - }, - Spec: infrav1beta1.PostgreSQLDatabaseSpec{ - DatabaseSpec: &infrav1beta1.DatabaseSpec{ - Address: "postgres://does-not-exist:27017", - RootSecret: &infrav1beta1.SecretReference{ - Name: rootSecret.Name, + Spec: infrav1beta1.PostgreSQLUserSpec{ + Database: &infrav1beta1.DatabaseReference{ + Name: "does-not-exist", + }, + Credentials: &infrav1beta1.SecretReference{ + Name: "does-not-exist", + }, }, - }, - }, - } - Expect(k8sClient.Create(context.Background(), createdDB)).Should(Succeed()) - }) - - It("adds user", func() { - keyUser = types.NamespacedName{ - Name: "postgresuser-" + randStringRunes(5), - Namespace: namespace.Name, - } - createdUser = &infrav1beta1.PostgreSQLUser{ - ObjectMeta: metav1.ObjectMeta{ - Name: keyUser.Name, - Namespace: keyUser.Namespace, - }, - Spec: infrav1beta1.PostgreSQLUserSpec{ - Database: &infrav1beta1.DatabaseReference{ - Name: keyDB.Name, - }, - Credentials: &infrav1beta1.SecretReference{ - Name: "does-not-exist", - }, - }, - } - Expect(k8sClient.Create(context.Background(), createdUser)).Should(Succeed()) - }) - - It("expecs reconcile to fail because database is not reachable", func() { - got := &infrav1beta1.PostgreSQLUser{} - Eventually(func() bool { - _ = k8sClient.Get(context.Background(), keyUser, got) - return len(got.Status.Conditions) == 1 && - got.Status.Conditions[0].Reason == infrav1beta1.ConnectionFailedReason && - got.Status.Conditions[0].Status == "False" && - got.Status.Conditions[0].Type == infrav1beta1.UserReadyConditionType - - }, timeout, interval).Should(BeTrue()) - }) - }) + } + + Expect(k8sClient.Create(context.Background(), createdUser)).Should(Succeed()) + }) + + It("fails reconcile because there is no database", func() { + got := &infrav1beta1.PostgreSQLUser{} + Eventually(func() bool { + _ = k8sClient.Get(context.Background(), keyUser, got) + + return len(got.Status.Conditions) == 1 && + got.Status.Conditions[0].Reason == infrav1beta1.DatabaseNotFoundReason && + got.Status.Conditions[0].Status == "False" && + got.Status.Conditions[0].Type == infrav1beta1.UserReadyConditionType + }, timeout, interval).Should(BeTrue()) + }) + }) - Describe("fails if user secret not found", Ordered, func() { - var ( - createdDB *infrav1beta1.PostgreSQLDatabase - createdUser *infrav1beta1.PostgreSQLUser - keyUser types.NamespacedName - keyDB types.NamespacedName - ) - - namespace, rootSecret := setupNamespace() - - It("adds database", func() { - keyDB = types.NamespacedName{ - Name: "postgresdatabase-" + randStringRunes(5), - Namespace: namespace.Name, - } - createdDB = &infrav1beta1.PostgreSQLDatabase{ - ObjectMeta: metav1.ObjectMeta{ - Name: keyDB.Name, - Namespace: keyDB.Namespace, - }, - Spec: infrav1beta1.PostgreSQLDatabaseSpec{ - DatabaseSpec: &infrav1beta1.DatabaseSpec{ - Address: container.URI, - RootSecret: &infrav1beta1.SecretReference{ - Name: rootSecret.Name, + Describe("fails if datatabase root secret not found", Ordered, func() { + var ( + createdDB *infrav1beta1.PostgreSQLDatabase + createdUser *infrav1beta1.PostgreSQLUser + keyUser types.NamespacedName + keyDB types.NamespacedName + ) + + namespace, _ := setupNamespace() + + It("creates database", func() { + keyDB = types.NamespacedName{ + Name: "postgresdatabase-" + randStringRunes(5), + Namespace: namespace.Name, + } + createdDB = &infrav1beta1.PostgreSQLDatabase{ + ObjectMeta: metav1.ObjectMeta{ + Name: keyDB.Name, + Namespace: keyDB.Namespace, }, - }, - }, - } - Expect(k8sClient.Create(context.Background(), createdDB)).Should(Succeed()) - }) - - It("adds user", func() { - keyUser = types.NamespacedName{ - Name: "postgresuser-" + randStringRunes(5), - Namespace: namespace.Name, - } - createdUser = &infrav1beta1.PostgreSQLUser{ - ObjectMeta: metav1.ObjectMeta{ - Name: keyUser.Name, - Namespace: keyUser.Namespace, - }, - Spec: infrav1beta1.PostgreSQLUserSpec{ - Database: &infrav1beta1.DatabaseReference{ - Name: keyDB.Name, - }, - Credentials: &infrav1beta1.SecretReference{ - Name: "does-not-exist", - }, - }, - } - Expect(k8sClient.Create(context.Background(), createdUser)).Should(Succeed()) - }) - - It("fails reconcile because user credentials are not found", func() { - got := &infrav1beta1.PostgreSQLUser{} - Eventually(func() bool { - _ = k8sClient.Get(context.Background(), keyUser, got) - return len(got.Status.Conditions) == 1 && - got.Status.Conditions[0].Reason == infrav1beta1.CredentialsNotFoundReason && - got.Status.Conditions[0].Status == "False" && - strings.Contains(got.Status.Conditions[0].Message, "referencing secret was not found:") && - got.Status.Conditions[0].Type == infrav1beta1.UserReadyConditionType - - }, timeout, interval).Should(BeTrue()) - }) - }) - - Describe("fails if user secret exists but fields not found", Ordered, func() { - var ( - createdDB *infrav1beta1.PostgreSQLDatabase - createdUser *infrav1beta1.PostgreSQLUser - keyUser types.NamespacedName - keyDB types.NamespacedName - createdSecret *v1.Secret - keySecret types.NamespacedName - password string - ) - - namespace, rootSecret := setupNamespace() - - It("adds database", func() { - keyDB = types.NamespacedName{ - Name: "postgresdatabase-" + randStringRunes(5), - Namespace: namespace.Name, - } - createdDB = &infrav1beta1.PostgreSQLDatabase{ - ObjectMeta: metav1.ObjectMeta{ - Name: keyDB.Name, - Namespace: keyDB.Namespace, - }, - Spec: infrav1beta1.PostgreSQLDatabaseSpec{ - DatabaseSpec: &infrav1beta1.DatabaseSpec{ - Address: container.URI, - RootSecret: &infrav1beta1.SecretReference{ - Name: rootSecret.Name, + Spec: infrav1beta1.PostgreSQLDatabaseSpec{ + DatabaseSpec: &infrav1beta1.DatabaseSpec{ + RootSecret: &infrav1beta1.SecretReference{ + Name: "does-not-exist", + }, + }, }, - }, - }, - } - Expect(k8sClient.Create(context.Background(), createdDB)).Should(Succeed()) - }) - - It("adds user", func() { - keySecret = types.NamespacedName{ - Name: "secret-" + randStringRunes(5), - Namespace: namespace.Name, - } - keyUser = types.NamespacedName{ - Name: "postgresuser-" + randStringRunes(5), - Namespace: namespace.Name, - } - createdUser = &infrav1beta1.PostgreSQLUser{ - ObjectMeta: metav1.ObjectMeta{ - Name: keyUser.Name, - Namespace: keyUser.Namespace, - }, - Spec: infrav1beta1.PostgreSQLUserSpec{ - Database: &infrav1beta1.DatabaseReference{ - Name: keyDB.Name, - }, - Credentials: &infrav1beta1.SecretReference{ - Name: keySecret.Name, - UserField: "does-not-exist", - }, - }, - } - Expect(k8sClient.Create(context.Background(), createdUser)).Should(Succeed()) - }) - - It("adds secret", func() { - password = randStringRunes(5) - createdSecret = &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: keySecret.Name, - Namespace: keySecret.Namespace, - }, - Data: map[string][]byte{ - "username": []byte(keyUser.Name), - "password": []byte(password), - }, - } - Expect(k8sClient.Create(context.Background(), createdSecret)).Should(Succeed()) - }) - - It("fails reconcile because user field in secret is not found", func() { - got := &infrav1beta1.PostgreSQLUser{} - Eventually(func() bool { - _ = k8sClient.Get(context.Background(), keyUser, got) - return len(got.Status.Conditions) == 1 && - got.Status.Conditions[0].Reason == infrav1beta1.CredentialsNotFoundReason && - got.Status.Conditions[0].Status == "False" && - strings.Contains(got.Status.Conditions[0].Message, "credentials field not found in referenced secret:") && - got.Status.Conditions[0].Type == infrav1beta1.UserReadyConditionType - - }, timeout, interval).Should(BeTrue()) - }) - }) - - Describe("Successful user creation", Ordered, func() { - var ( - createdDB *infrav1beta1.PostgreSQLDatabase - createdUser *infrav1beta1.PostgreSQLUser - createdSecret *v1.Secret - keyUser types.NamespacedName - keyDB types.NamespacedName - keySecret types.NamespacedName - password string - ) - - namespace, rootSecret := setupNamespace() - - Describe("creates readWrite user if it does not exists", Ordered, func() { - var ( - client *pgxpool.Pool - ) - - It("adds database", func() { - keyDB = types.NamespacedName{ - Name: "postgresdatabase-" + randStringRunes(5), - Namespace: namespace.Name, - } - createdDB = &infrav1beta1.PostgreSQLDatabase{ - ObjectMeta: metav1.ObjectMeta{ - Name: keyDB.Name, - Namespace: keyDB.Namespace, - }, - Spec: infrav1beta1.PostgreSQLDatabaseSpec{ - DatabaseSpec: &infrav1beta1.DatabaseSpec{ - Address: container.URI, - RootSecret: &infrav1beta1.SecretReference{ - Name: rootSecret.Name, + } + Expect(k8sClient.Create(context.Background(), createdDB)).Should(Succeed()) + }) + + It("adds user", func() { + keyUser = types.NamespacedName{ + Name: "postgresuser-" + randStringRunes(5), + Namespace: namespace.Name, + } + createdUser = &infrav1beta1.PostgreSQLUser{ + ObjectMeta: metav1.ObjectMeta{ + Name: keyUser.Name, + Namespace: keyUser.Namespace, + }, + Spec: infrav1beta1.PostgreSQLUserSpec{ + Database: &infrav1beta1.DatabaseReference{ + Name: keyDB.Name, + }, + Credentials: &infrav1beta1.SecretReference{ + Name: "does-not-exist", }, }, - }, - } - - Expect(k8sClient.Create(context.Background(), createdDB)).Should(Succeed()) + } + Expect(k8sClient.Create(context.Background(), createdUser)).Should(Succeed()) + }) + + It("fails reconcile because the db root secret is not found", func() { + got := &infrav1beta1.PostgreSQLUser{} + Eventually(func() bool { + _ = k8sClient.Get(context.Background(), keyUser, got) + + return len(got.Status.Conditions) == 1 && + got.Status.Conditions[0].Reason == infrav1beta1.CredentialsNotFoundReason && + got.Status.Conditions[0].Status == "False" && + got.Status.Conditions[0].Type == infrav1beta1.UserReadyConditionType + + }, timeout, interval).Should(BeTrue()) + }) }) - It("adds user", func() { - keySecret = types.NamespacedName{ - Name: "secret-" + randStringRunes(5), - Namespace: namespace.Name, - } - keyUser = types.NamespacedName{ - Name: "postgresuser-" + randStringRunes(5), - Namespace: namespace.Name, - } - createdUser = &infrav1beta1.PostgreSQLUser{ - ObjectMeta: metav1.ObjectMeta{ - Name: keyUser.Name, - Namespace: keyUser.Namespace, - }, - Spec: infrav1beta1.PostgreSQLUserSpec{ - Database: &infrav1beta1.DatabaseReference{ - Name: keyDB.Name, + Describe("fails if datatabase root secret not found", Ordered, func() { + var ( + createdDB *infrav1beta1.PostgreSQLDatabase + createdUser *infrav1beta1.PostgreSQLUser + keyUser types.NamespacedName + keyDB types.NamespacedName + ) + + namespace, rootSecret := setupNamespace() + + It("adds database", func() { + keyDB = types.NamespacedName{ + Name: "postgresdatabase-" + randStringRunes(5), + Namespace: namespace.Name, + } + createdDB = &infrav1beta1.PostgreSQLDatabase{ + ObjectMeta: metav1.ObjectMeta{ + Name: keyDB.Name, + Namespace: keyDB.Namespace, }, - Credentials: &infrav1beta1.SecretReference{ - Name: keySecret.Name, + Spec: infrav1beta1.PostgreSQLDatabaseSpec{ + DatabaseSpec: &infrav1beta1.DatabaseSpec{ + Address: "postgres://does-not-exist:27017", + RootSecret: &infrav1beta1.SecretReference{ + Name: rootSecret.Name, + }, + }, }, - }, - } - Expect(k8sClient.Create(context.Background(), createdUser)).Should(Succeed()) - }) - - It("adds secret", func() { - password = randStringRunes(5) - createdSecret = &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: keySecret.Name, - Namespace: keySecret.Namespace, - }, - Data: map[string][]byte{ - "username": []byte(keyUser.Name), - "password": []byte(password), - }, - } - Expect(k8sClient.Create(context.Background(), createdSecret)).Should(Succeed()) - }) - - It("expects ready user", func() { - got := &infrav1beta1.PostgreSQLUser{} - Eventually(func() bool { - _ = k8sClient.Get(context.Background(), keyUser, got) - return len(got.Status.Conditions) == 1 && - got.Status.Conditions[0].Reason == infrav1beta1.UserProvisioningSuccessfulReason && - got.Status.Conditions[0].Status == "True" && - got.Status.Conditions[0].Type == infrav1beta1.UserReadyConditionType - - }, timeout, interval).Should(BeTrue()) - }) - - It("can access the created database", func() { - popt, err := url.Parse(container.URI) - Expect(err).NotTo(HaveOccurred(), "failed to parse postgresql uri") - - popt.User = url.UserPassword(keyUser.Name, password) - q, _ := url.ParseQuery(popt.RawQuery) - q.Add("connect_timeout", "2") - popt.RawQuery = q.Encode() - popt.Path = keyDB.Name - - Expect(err).NotTo(HaveOccurred(), "failed to connecto to postgresql") - - Eventually(func() error { - client, err = pgxpool.Connect(ctx, popt.String()) - return err - }, timeout, interval).Should(Succeed()) - }) - - It("has write access to the referenced role database", func() { - _, err := client.Exec(ctx, fmt.Sprintln("CREATE TABLE foo (key integer);")) - Expect(err).NotTo(HaveOccurred(), "failed to insert doc") - }) - - It("has has no access to another database", func() { - popt, err := url.Parse(container.URI) - Expect(err).NotTo(HaveOccurred(), "failed to parse postgresql uri") - - popt.User = url.UserPassword(keyUser.Name, password) - q, _ := url.ParseQuery(popt.RawQuery) - q.Add("connect_timeout", "2") - popt.RawQuery = q.Encode() - popt.Path = "does-not-exist" - - Expect(err).NotTo(HaveOccurred(), "failed to connecto to postgresql") - - Eventually(func() error { - client, err = pgxpool.Connect(ctx, popt.String()) - return err - }, timeout, interval).ShouldNot(Succeed()) - }) - - It("can't access the created database with invalid credentials", func() { - popt, err := url.Parse(container.URI) - Expect(err).NotTo(HaveOccurred(), "failed to parse postgresql uri") - - popt.User = url.UserPassword(keyUser.Name, "invalid-password") - q, _ := url.ParseQuery(popt.RawQuery) - q.Add("connect_timeout", "2") - popt.RawQuery = q.Encode() - popt.Path = keyDB.Name - - Expect(err).NotTo(HaveOccurred(), "failed to connecto to postgresql") - - Eventually(func() error { - _, err = pgxpool.Connect(ctx, popt.String()) - return err - }, timeout, interval).ShouldNot(Succeed()) - }) - }) - - Describe("Change password for user", Ordered, func() { - It("changes password in referenced user secret", func() { - password = randStringRunes(5) - createdSecret.Data = map[string][]byte{ - "username": []byte(createdUser.ObjectMeta.Name), - "password": []byte(password), - } - Expect(k8sClient.Update(context.Background(), createdSecret)).Should(Succeed()) - }) - - It("expects ready user", func() { - got := &infrav1beta1.PostgreSQLUser{} - Eventually(func() bool { - _ = k8sClient.Get(context.Background(), keyUser, got) - return len(got.Status.Conditions) == 1 && - got.Status.Conditions[0].Reason == infrav1beta1.UserProvisioningSuccessfulReason && - got.Status.Conditions[0].Status == "True" && - got.Status.Conditions[0].Type == infrav1beta1.UserReadyConditionType - - }, timeout, interval).Should(BeTrue()) - }) - - It("can access the database with the new password", func() { - popt, err := url.Parse(container.URI) - Expect(err).NotTo(HaveOccurred(), "failed to parse postgresql uri") - - popt.User = url.UserPassword(keyUser.Name, password) - q, _ := url.ParseQuery(popt.RawQuery) - q.Add("connect_timeout", "2") - popt.RawQuery = q.Encode() - popt.Path = keyDB.Name - - Expect(err).NotTo(HaveOccurred(), "failed to connecto to postgresql") - - Eventually(func() error { - _, err = pgxpool.Connect(ctx, popt.String()) - return err - }, timeout, interval).Should(Succeed()) + } + Expect(k8sClient.Create(context.Background(), createdDB)).Should(Succeed()) + }) + + It("adds user", func() { + keyUser = types.NamespacedName{ + Name: "postgresuser-" + randStringRunes(5), + Namespace: namespace.Name, + } + createdUser = &infrav1beta1.PostgreSQLUser{ + ObjectMeta: metav1.ObjectMeta{ + Name: keyUser.Name, + Namespace: keyUser.Namespace, + }, + Spec: infrav1beta1.PostgreSQLUserSpec{ + Database: &infrav1beta1.DatabaseReference{ + Name: keyDB.Name, + }, + Credentials: &infrav1beta1.SecretReference{ + Name: "does-not-exist", + }, + }, + } + Expect(k8sClient.Create(context.Background(), createdUser)).Should(Succeed()) + }) + + It("expecs reconcile to fail because database is not reachable", func() { + got := &infrav1beta1.PostgreSQLUser{} + Eventually(func() bool { + _ = k8sClient.Get(context.Background(), keyUser, got) + return len(got.Status.Conditions) == 1 && + got.Status.Conditions[0].Reason == infrav1beta1.ConnectionFailedReason && + got.Status.Conditions[0].Status == "False" && + got.Status.Conditions[0].Type == infrav1beta1.UserReadyConditionType + + }, timeout, interval).Should(BeTrue()) + }) }) - }) - Describe("Delete user removes user from postgres", Ordered, func() { - It("deletes user", func() { - Expect(k8sClient.Delete(context.Background(), createdUser)).Should(Succeed()) + Describe("fails if user secret not found", Ordered, func() { + var ( + createdDB *infrav1beta1.PostgreSQLDatabase + createdUser *infrav1beta1.PostgreSQLUser + keyUser types.NamespacedName + keyDB types.NamespacedName + ) + + namespace, rootSecret := setupNamespace() + + It("adds database", func() { + keyDB = types.NamespacedName{ + Name: "postgresdatabase-" + randStringRunes(5), + Namespace: namespace.Name, + } + createdDB = &infrav1beta1.PostgreSQLDatabase{ + ObjectMeta: metav1.ObjectMeta{ + Name: keyDB.Name, + Namespace: keyDB.Namespace, + }, + Spec: infrav1beta1.PostgreSQLDatabaseSpec{ + DatabaseSpec: &infrav1beta1.DatabaseSpec{ + Address: container.URI, + RootSecret: &infrav1beta1.SecretReference{ + Name: rootSecret.Name, + }, + }, + }, + } + Expect(k8sClient.Create(context.Background(), createdDB)).Should(Succeed()) + }) + + It("adds user", func() { + keyUser = types.NamespacedName{ + Name: "postgresuser-" + randStringRunes(5), + Namespace: namespace.Name, + } + createdUser = &infrav1beta1.PostgreSQLUser{ + ObjectMeta: metav1.ObjectMeta{ + Name: keyUser.Name, + Namespace: keyUser.Namespace, + }, + Spec: infrav1beta1.PostgreSQLUserSpec{ + Database: &infrav1beta1.DatabaseReference{ + Name: keyDB.Name, + }, + Credentials: &infrav1beta1.SecretReference{ + Name: "does-not-exist", + }, + }, + } + Expect(k8sClient.Create(context.Background(), createdUser)).Should(Succeed()) + }) + + It("fails reconcile because user credentials are not found", func() { + got := &infrav1beta1.PostgreSQLUser{} + Eventually(func() bool { + _ = k8sClient.Get(context.Background(), keyUser, got) + return len(got.Status.Conditions) == 1 && + got.Status.Conditions[0].Reason == infrav1beta1.CredentialsNotFoundReason && + got.Status.Conditions[0].Status == "False" && + strings.Contains(got.Status.Conditions[0].Message, "referencing secret was not found:") && + got.Status.Conditions[0].Type == infrav1beta1.UserReadyConditionType + + }, timeout, interval).Should(BeTrue()) + }) }) - It("expects gone", func() { - got := &infrav1beta1.PostgreSQLUser{} - Eventually(func() error { - return k8sClient.Get(context.Background(), keyUser, got) - }, timeout, interval).ShouldNot(Succeed()) + Describe("fails if user secret exists but fields not found", Ordered, func() { + var ( + createdDB *infrav1beta1.PostgreSQLDatabase + createdUser *infrav1beta1.PostgreSQLUser + keyUser types.NamespacedName + keyDB types.NamespacedName + createdSecret *corev1.Secret + keySecret types.NamespacedName + password string + ) + + namespace, rootSecret := setupNamespace() + + It("adds database", func() { + keyDB = types.NamespacedName{ + Name: "postgresdatabase-" + randStringRunes(5), + Namespace: namespace.Name, + } + createdDB = &infrav1beta1.PostgreSQLDatabase{ + ObjectMeta: metav1.ObjectMeta{ + Name: keyDB.Name, + Namespace: keyDB.Namespace, + }, + Spec: infrav1beta1.PostgreSQLDatabaseSpec{ + DatabaseSpec: &infrav1beta1.DatabaseSpec{ + Address: container.URI, + RootSecret: &infrav1beta1.SecretReference{ + Name: rootSecret.Name, + }, + }, + }, + } + Expect(k8sClient.Create(context.Background(), createdDB)).Should(Succeed()) + }) + + It("adds user", func() { + keySecret = types.NamespacedName{ + Name: "secret-" + randStringRunes(5), + Namespace: namespace.Name, + } + keyUser = types.NamespacedName{ + Name: "postgresuser-" + randStringRunes(5), + Namespace: namespace.Name, + } + createdUser = &infrav1beta1.PostgreSQLUser{ + ObjectMeta: metav1.ObjectMeta{ + Name: keyUser.Name, + Namespace: keyUser.Namespace, + }, + Spec: infrav1beta1.PostgreSQLUserSpec{ + Database: &infrav1beta1.DatabaseReference{ + Name: keyDB.Name, + }, + Credentials: &infrav1beta1.SecretReference{ + Name: keySecret.Name, + UserField: "does-not-exist", + }, + }, + } + Expect(k8sClient.Create(context.Background(), createdUser)).Should(Succeed()) + }) + + It("adds secret", func() { + password = randStringRunes(5) + createdSecret = &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: keySecret.Name, + Namespace: keySecret.Namespace, + }, + Data: map[string][]byte{ + "username": []byte(keyUser.Name), + "password": []byte(password), + }, + } + Expect(k8sClient.Create(context.Background(), createdSecret)).Should(Succeed()) + }) + + It("fails reconcile because user field in secret is not found", func() { + got := &infrav1beta1.PostgreSQLUser{} + Eventually(func() bool { + _ = k8sClient.Get(context.Background(), keyUser, got) + return len(got.Status.Conditions) == 1 && + got.Status.Conditions[0].Reason == infrav1beta1.CredentialsNotFoundReason && + got.Status.Conditions[0].Status == "False" && + strings.Contains(got.Status.Conditions[0].Message, "credentials field not found in referenced secret:") && + got.Status.Conditions[0].Type == infrav1beta1.UserReadyConditionType + + }, timeout, interval).Should(BeTrue()) + }) }) - It("can't authenticate anymore since the user is deleted", func() { - popt, err := url.Parse(container.URI) - Expect(err).NotTo(HaveOccurred(), "failed to parse postgresql uri") - - popt.User = url.UserPassword(keyUser.Name, password) - q, _ := url.ParseQuery(popt.RawQuery) - q.Add("connect_timeout", "2") - popt.RawQuery = q.Encode() - popt.Path = keyDB.Name - - Expect(err).NotTo(HaveOccurred(), "failed to connecto to postgresql") - - Eventually(func() error { - _, err = pgxpool.Connect(ctx, popt.String()) - return err - }, timeout, interval).ShouldNot(Succeed()) + Describe("Successful user creation", Ordered, func() { + var ( + createdDB *infrav1beta1.PostgreSQLDatabase + createdUser *infrav1beta1.PostgreSQLUser + createdSecret *corev1.Secret + keyUser types.NamespacedName + keyDB types.NamespacedName + keySecret types.NamespacedName + password string + ) + + namespace, rootSecret := setupNamespace() + + Describe("creates readWrite user if it does not exists", Ordered, func() { + var ( + client *pgxpool.Pool + ) + + It("adds database", func() { + keyDB = types.NamespacedName{ + Name: "postgresdatabase-" + randStringRunes(5), + Namespace: namespace.Name, + } + createdDB = &infrav1beta1.PostgreSQLDatabase{ + ObjectMeta: metav1.ObjectMeta{ + Name: keyDB.Name, + Namespace: keyDB.Namespace, + }, + Spec: infrav1beta1.PostgreSQLDatabaseSpec{ + DatabaseSpec: &infrav1beta1.DatabaseSpec{ + Address: container.URI, + RootSecret: &infrav1beta1.SecretReference{ + Name: rootSecret.Name, + }, + }, + }, + } + + Expect(k8sClient.Create(context.Background(), createdDB)).Should(Succeed()) + }) + + It("adds user", func() { + keySecret = types.NamespacedName{ + Name: "secret-" + randStringRunes(5), + Namespace: namespace.Name, + } + keyUser = types.NamespacedName{ + Name: "postgresuser-" + randStringRunes(5), + Namespace: namespace.Name, + } + createdUser = &infrav1beta1.PostgreSQLUser{ + ObjectMeta: metav1.ObjectMeta{ + Name: keyUser.Name, + Namespace: keyUser.Namespace, + }, + Spec: infrav1beta1.PostgreSQLUserSpec{ + Database: &infrav1beta1.DatabaseReference{ + Name: keyDB.Name, + }, + Credentials: &infrav1beta1.SecretReference{ + Name: keySecret.Name, + }, + }, + } + Expect(k8sClient.Create(context.Background(), createdUser)).Should(Succeed()) + }) + + It("adds secret", func() { + password = randStringRunes(5) + createdSecret = &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: keySecret.Name, + Namespace: keySecret.Namespace, + }, + Data: map[string][]byte{ + "username": []byte(keyUser.Name), + "password": []byte(password), + }, + } + Expect(k8sClient.Create(context.Background(), createdSecret)).Should(Succeed()) + }) + + It("expects ready user", func() { + got := &infrav1beta1.PostgreSQLUser{} + Eventually(func() bool { + _ = k8sClient.Get(context.Background(), keyUser, got) + return len(got.Status.Conditions) == 1 && + got.Status.Conditions[0].Reason == infrav1beta1.UserProvisioningSuccessfulReason && + got.Status.Conditions[0].Status == "True" && + got.Status.Conditions[0].Type == infrav1beta1.UserReadyConditionType + + }, timeout, interval).Should(BeTrue()) + }) + + It("can access the created database", func() { + popt, err := url.Parse(container.URI) + Expect(err).NotTo(HaveOccurred(), "failed to parse postgresql uri") + + popt.User = url.UserPassword(keyUser.Name, password) + q, _ := url.ParseQuery(popt.RawQuery) + q.Add("connect_timeout", "2") + popt.RawQuery = q.Encode() + popt.Path = keyDB.Name + + Expect(err).NotTo(HaveOccurred(), "failed to connect to postgresql") + + Eventually(func() error { + client, err = pgxpool.Connect(ctx, popt.String()) + return err + }, timeout, interval).Should(Succeed()) + }) + + It("has write access to the referenced role database", func() { + _, err := client.Exec(ctx, fmt.Sprintln("CREATE TABLE foo (key integer);")) + Expect(err).NotTo(HaveOccurred(), "failed to insert doc") + }) + + It("has has no access to another database", func() { + popt, err := url.Parse(container.URI) + Expect(err).NotTo(HaveOccurred(), "failed to parse postgresql uri") + + popt.User = url.UserPassword(keyUser.Name, password) + q, _ := url.ParseQuery(popt.RawQuery) + q.Add("connect_timeout", "2") + popt.RawQuery = q.Encode() + popt.Path = "does-not-exist" + + Expect(err).NotTo(HaveOccurred(), "failed to connect to postgresql") + + Eventually(func() error { + client, err = pgxpool.Connect(ctx, popt.String()) + return err + }, timeout, interval).ShouldNot(Succeed()) + }) + + It("can't access the created database with invalid credentials", func() { + popt, err := url.Parse(container.URI) + Expect(err).NotTo(HaveOccurred(), "failed to parse postgresql uri") + + popt.User = url.UserPassword(keyUser.Name, "invalid-password") + q, _ := url.ParseQuery(popt.RawQuery) + q.Add("connect_timeout", "2") + popt.RawQuery = q.Encode() + popt.Path = keyDB.Name + + Expect(err).NotTo(HaveOccurred(), "failed to connect to postgresql") + + Eventually(func() error { + _, err = pgxpool.Connect(ctx, popt.String()) + return err + }, timeout, interval).ShouldNot(Succeed()) + }) + }) + + Describe("Change password for user", Ordered, func() { + It("changes password in referenced user secret", func() { + password = randStringRunes(5) + createdSecret.Data = map[string][]byte{ + "username": []byte(createdUser.ObjectMeta.Name), + "password": []byte(password), + } + Expect(k8sClient.Update(context.Background(), createdSecret)).Should(Succeed()) + }) + + It("expects ready user", func() { + got := &infrav1beta1.PostgreSQLUser{} + Eventually(func() bool { + _ = k8sClient.Get(context.Background(), keyUser, got) + return len(got.Status.Conditions) == 1 && + got.Status.Conditions[0].Reason == infrav1beta1.UserProvisioningSuccessfulReason && + got.Status.Conditions[0].Status == "True" && + got.Status.Conditions[0].Type == infrav1beta1.UserReadyConditionType + + }, timeout, interval).Should(BeTrue()) + }) + + It("can access the database with the new password", func() { + popt, err := url.Parse(container.URI) + Expect(err).NotTo(HaveOccurred(), "failed to parse postgresql uri") + + popt.User = url.UserPassword(keyUser.Name, password) + q, _ := url.ParseQuery(popt.RawQuery) + q.Add("connect_timeout", "2") + popt.RawQuery = q.Encode() + popt.Path = keyDB.Name + + Expect(err).NotTo(HaveOccurred(), "failed to connect to postgresql") + + Eventually(func() error { + _, err = pgxpool.Connect(ctx, popt.String()) + return err + }, timeout, interval).Should(Succeed()) + }) + }) + + Describe("Delete user removes user from postgres", Ordered, func() { + It("deletes user", func() { + Expect(k8sClient.Delete(context.Background(), createdUser)).Should(Succeed()) + }) + + It("expects gone", func() { + got := &infrav1beta1.PostgreSQLUser{} + Eventually(func() error { + return k8sClient.Get(context.Background(), keyUser, got) + }, timeout, interval).ShouldNot(Succeed()) + }) + + It("can't authenticate anymore since the user is deleted", func() { + popt, err := url.Parse(container.URI) + Expect(err).NotTo(HaveOccurred(), "failed to parse postgresql uri") + + popt.User = url.UserPassword(keyUser.Name, password) + q, _ := url.ParseQuery(popt.RawQuery) + q.Add("connect_timeout", "2") + popt.RawQuery = q.Encode() + popt.Path = keyDB.Name + + Expect(err).NotTo(HaveOccurred(), "failed to connect to postgresql") + + Eventually(func() error { + _, err = pgxpool.Connect(ctx, popt.String()) + return err + }, timeout, interval).ShouldNot(Succeed()) + }) + }) }) }) - }) + } }) diff --git a/controllers/postgresqluser_controller.go b/controllers/postgresqluser_controller.go index 96f3aae..d03a3cb 100644 --- a/controllers/postgresqluser_controller.go +++ b/controllers/postgresqluser_controller.go @@ -235,7 +235,30 @@ func (r *PostgreSQLUserReconciler) reconcile(ctx context.Context, user infrav1be return user, err } - err = dbHandler.SetupUser(ctx, db.GetDatabaseName(), usr, pw) + var grants []database.Grant + for _, grant := range user.Spec.Grants { + var privs []database.Privilege + for _, p := range grant.Privileges { + privs = append(privs, database.Privilege(p)) + } + + grants = append(grants, database.Grant{ + Object: grant.Object, + ObjectName: grant.ObjectName, + User: grant.User, + Privileges: privs, + }) + } + + userSpec := database.PostgresqlUser{ + Database: db.GetDatabaseName(), + Username: usr, + Password: pw, + Roles: user.Spec.Roles, + Grants: grants, + } + + err = dbHandler.SetupUser(ctx, userSpec) if err != nil { err = fmt.Errorf("Failed to provison user account: %w", err) infrav1beta1.UserNotReadyCondition(&user, infrav1beta1.ConnectionFailedReason, err.Error()) @@ -263,15 +286,21 @@ func (r *PostgreSQLUserReconciler) finalizeUser(ctx context.Context, user infrav //We can't easily drop a user from postgres since it ownes objects //err := userDropper.DropUser(ctx, db.GetDatabaseName(), user.Status.Username) + userSpec := database.PostgresqlUser{ + Database: db.GetDatabaseName(), + Username: user.Status.Username, + Password: generateToken(32), + } + //Instead privileges are revoked and the password gets randomized - err := dbHandler.SetupUser(ctx, db.GetDatabaseName(), user.Status.Username, generateToken(32)) + err := dbHandler.SetupUser(ctx, userSpec) if err != nil { err = fmt.Errorf("Failed to update user account: %w", err) infrav1beta1.UserNotReadyCondition(&user, infrav1beta1.ConnectionFailedReason, err.Error()) return user, err } - err = dbHandler.RevokeAllPrivileges(ctx, db.GetDatabaseName(), user.Status.Username) + err = dbHandler.RevokeAllPrivileges(ctx, userSpec) if err != nil { err = fmt.Errorf("Failed to revoke privileges from user account: %w", err) diff --git a/go.mod b/go.mod index bda250b..e193f61 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/onsi/gomega v1.27.6 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.15.0 - github.com/testcontainers/testcontainers-go v0.19.0 + github.com/testcontainers/testcontainers-go v0.20.1 go.mongodb.org/atlas v0.25.0 go.mongodb.org/mongo-driver v1.11.4 k8s.io/api v0.26.4 @@ -29,7 +29,7 @@ require ( github.com/cpuguy83/dockercfg v0.3.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/docker/distribution v2.8.1+incompatible // indirect - github.com/docker/docker v23.0.1+incompatible // indirect + github.com/docker/docker v23.0.5+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/emicklei/go-restful/v3 v3.9.0 // indirect @@ -77,7 +77,7 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc2 // indirect - github.com/opencontainers/runc v1.1.3 // indirect + github.com/opencontainers/runc v1.1.5 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.14.0 // indirect @@ -100,7 +100,7 @@ require ( golang.org/x/net v0.8.0 // indirect golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.6.0 // indirect + golang.org/x/sys v0.7.0 // indirect golang.org/x/term v0.6.0 // indirect golang.org/x/text v0.8.0 // indirect golang.org/x/time v0.3.0 // indirect diff --git a/go.sum b/go.sum index 5bfac20..e28b3b7 100644 --- a/go.sum +++ b/go.sum @@ -91,8 +91,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v23.0.1+incompatible h1:vjgvJZxprTTE1A37nm+CLNAdwu6xZekyoiVlUZEINcY= -github.com/docker/docker v23.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v23.0.5+incompatible h1:DaxtlTJjFSnLOXVNUBU1+6kXGz2lpDoEAH6QoxaSg8k= +github.com/docker/docker v23.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= @@ -370,8 +370,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= -github.com/opencontainers/runc v1.1.3 h1:vIXrkId+0/J2Ymu2m7VjGvbSlAId9XNRPhn2p4b+d8w= -github.com/opencontainers/runc v1.1.3/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= +github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs= +github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= @@ -458,8 +458,8 @@ github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/testcontainers/testcontainers-go v0.19.0 h1:3bmFPuQRgVIQwxZJERyzB8AogmJW3Qzh8iDyfJbPhi8= -github.com/testcontainers/testcontainers-go v0.19.0/go.mod h1:3YsSoxK0rGEUzbGD4gUVt1Nm3GJpCIq94GX+2LSf3d4= +github.com/testcontainers/testcontainers-go v0.20.1 h1:mK15UPJ8c5P+NsQKmkqzs/jMdJt6JMs5vlw2y4j92c0= +github.com/testcontainers/testcontainers-go v0.20.1/go.mod h1:zb+NOlCQBkZ7RQp4QI+YMIHyO2CQ/qsXzNF5eLJ24SY= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= @@ -692,8 +692,8 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=