Skip to content

Commit

Permalink
feat(clickhousedatabase): add 'databasename' to spec
Browse files Browse the repository at this point in the history
This makes it possible to use UTF-8 charset with ClickhouseDatabase resources.
  • Loading branch information
rriski committed Jun 17, 2024
1 parent 57a750a commit 7ef19b3
Show file tree
Hide file tree
Showing 11 changed files with 129 additions and 103 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## [MAJOR.MINOR.PATCH] - YYYY-MM-DD

- Add `ClickhouseDatabase` field `databaseName`, type `string`: Name of the Clickhouse database. If not
set, `metadata.name` is used

## v0.20.0 - 2024-06-05

- Add kind: `ServiceIntegrationEndpoint`
Expand Down
14 changes: 14 additions & 0 deletions api/v1alpha1/clickhousedatabase_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import (
// ClickhouseDatabaseSpec defines the desired state of ClickhouseDatabase
type ClickhouseDatabaseSpec struct {
ServiceDependant `json:",inline"`

// Specifies the Clickhouse database name. Defaults to `metadata.name` if omitted.
// Note: `metadata.name` is ASCII-only. For UTF-8 names, use spec.DatabaseName, but ASCII is advised for compatibility.
DatabaseName string `json:"databaseName,omitempty"`
}

// ClickhouseDatabaseStatus defines the observed state of ClickhouseDatabase
Expand All @@ -33,6 +37,16 @@ type ClickhouseDatabase struct {

var _ AivenManagedObject = &ClickhouseDatabase{}

func (in *ClickhouseDatabase) GetName() string {
// Default to Spec.Username and use ObjectMeta.Name if empty.
// ObjectMeta.Name doesn't support UTF-8 characters, Spec.Username does.
name := in.Spec.DatabaseName
if name == "" {
name = in.ObjectMeta.Name
}
return name
}

func (*ClickhouseDatabase) NoSecret() bool {
return true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ spec:
- key
- name
type: object
databaseName:
description: |-
Specifies the Clickhouse database name. Defaults to `metadata.name` if omitted.
Note: `metadata.name` is ASCII-only. For UTF-8 names, use spec.DatabaseName, but ASCII is advised for compatibility.
type: string
project:
description: Identifies the project this resource belongs to
maxLength: 63
Expand Down
5 changes: 5 additions & 0 deletions config/crd/bases/aiven.io_clickhousedatabases.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ spec:
- key
- name
type: object
databaseName:
description: |-
Specifies the Clickhouse database name. Defaults to `metadata.name` if omitted.
Note: `metadata.name` is ASCII-only. For UTF-8 names, use spec.DatabaseName, but ASCII is advised for compatibility.
type: string
project:
description: Identifies the project this resource belongs to
maxLength: 63
Expand Down
11 changes: 7 additions & 4 deletions controllers/clickhousedatabase_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,10 @@ func (h *ClickhouseDatabaseHandler) createOrUpdate(ctx context.Context, avn *aiv
return err
}

_, err = avn.ClickhouseDatabase.Get(ctx, db.Spec.Project, db.Spec.ServiceName, db.Name)
dbName := db.GetName()
_, err = avn.ClickhouseDatabase.Get(ctx, db.Spec.Project, db.Spec.ServiceName, dbName)
if isNotFound(err) {
err = avn.ClickhouseDatabase.Create(ctx, db.Spec.Project, db.Spec.ServiceName, db.Name)
err = avn.ClickhouseDatabase.Create(ctx, db.Spec.Project, db.Spec.ServiceName, dbName)
}

if err != nil {
Expand All @@ -79,7 +80,8 @@ func (h *ClickhouseDatabaseHandler) delete(ctx context.Context, avn *aiven.Clien
return false, err
}

err = avn.ClickhouseDatabase.Delete(ctx, db.Spec.Project, db.Spec.ServiceName, db.Name)
dbName := db.GetName()
err = avn.ClickhouseDatabase.Delete(ctx, db.Spec.Project, db.Spec.ServiceName, dbName)
if err != nil && !isNotFound(err) {
return false, err
}
Expand All @@ -93,7 +95,8 @@ func (h *ClickhouseDatabaseHandler) get(ctx context.Context, avn *aiven.Client,
return nil, err
}

_, err = avn.ClickhouseDatabase.Get(ctx, db.Spec.Project, db.Spec.ServiceName, db.Name)
dbName := db.GetName()
_, err = avn.ClickhouseDatabase.Get(ctx, db.Spec.Project, db.Spec.ServiceName, dbName)
if err != nil {
return nil, err
}
Expand Down
13 changes: 11 additions & 2 deletions docs/docs/api-reference/clickhouse.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,22 @@ title: "Clickhouse"
key: token

connInfoSecretTarget:
name: clickhouse-secret
prefix: MY_SECRET_PREFIX_
name: my-clickhouse
annotations:
foo: bar
labels:
baz: egg

tags:
env: test
instance: foo

userConfig:
ip_filter:
- network: 0.0.0.0/32
description: bar
- network: 10.20.0.0/16

project: my-aiven-project
cloudName: google-europe-west1
plan: startup-16
Expand Down
5 changes: 4 additions & 1 deletion docs/docs/api-reference/clickhousedatabase.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ title: "ClickhouseDatabase"
name: aiven-token
key: token

project: aiven-project-name
project: my-aiven-project
serviceName: my-clickhouse
databaseName: my-db
```

## ClickhouseDatabase {: #ClickhouseDatabase }
Expand Down Expand Up @@ -44,6 +45,8 @@ ClickhouseDatabaseSpec defines the desired state of ClickhouseDatabase.
**Optional**

- [`authSecretRef`](#spec.authSecretRef-property){: name='spec.authSecretRef-property'} (object). Authentication reference to Aiven token in a secret. See below for [nested schema](#spec.authSecretRef).
- [`databaseName`](#spec.databaseName-property){: name='spec.databaseName-property'} (string). Specifies the Clickhouse database name. Defaults to `metadata.name` if omitted.
Note: `metadata.name` is ASCII-only. For UTF-8 names, use spec.DatabaseName, but ASCII is advised for compatibility.

## authSecretRef {: #spec.authSecretRef }

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/api-reference/clickhouserole.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ title: "ClickhouseRole"

project: my-aiven-project
serviceName: my-clickhouse
role: my_role
role: my-role
```

## ClickhouseRole {: #ClickhouseRole }
Expand Down
13 changes: 11 additions & 2 deletions docs/docs/api-reference/examples/clickhouse.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,22 @@ spec:
key: token

connInfoSecretTarget:
name: clickhouse-secret
prefix: MY_SECRET_PREFIX_
name: my-clickhouse
annotations:
foo: bar
labels:
baz: egg

tags:
env: test
instance: foo

userConfig:
ip_filter:
- network: 0.0.0.0/32
description: bar
- network: 10.20.0.0/16

project: my-aiven-project
cloudName: google-europe-west1
plan: startup-16
Expand Down
3 changes: 2 additions & 1 deletion docs/docs/api-reference/examples/clickhousedatabase.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ spec:
name: aiven-token
key: token

project: aiven-project-name
project: my-aiven-project
serviceName: my-clickhouse
databaseName: my-db
158 changes: 66 additions & 92 deletions tests/clickhouse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,91 +12,6 @@ import (
"github.com/aiven/aiven-operator/controllers"
)

func getClickhouseYaml(project, cloudName, chName, dbName1, dbName2, role1, role2 string) string {
return fmt.Sprintf(`
apiVersion: aiven.io/v1alpha1
kind: Clickhouse
metadata:
name: %[3]s
spec:
authSecretRef:
name: aiven-token
key: token
project: %[1]s
cloudName: %[2]s
plan: startup-16
tags:
env: test
instance: foo
userConfig:
ip_filter:
- network: 0.0.0.0/32
description: bar
- network: 10.20.0.0/16
---
apiVersion: aiven.io/v1alpha1
kind: ClickhouseDatabase
metadata:
name: %[4]s
spec:
authSecretRef:
name: aiven-token
key: token
project: %[1]s
serviceName: %[3]s
---
apiVersion: aiven.io/v1alpha1
kind: ClickhouseDatabase
metadata:
name: %[5]s
spec:
authSecretRef:
name: aiven-token
key: token
project: %[1]s
serviceName: %[3]s
---
apiVersion: aiven.io/v1alpha1
kind: ClickhouseRole
metadata:
name: %[6]s
spec:
authSecretRef:
name: aiven-token
key: token
project: %[1]s
serviceName: %[3]s
role: reader
---
apiVersion: aiven.io/v1alpha1
kind: ClickhouseRole
metadata:
name: %[7]s
spec:
authSecretRef:
name: aiven-token
key: token
project: %[1]s
serviceName: %[3]s
role: writer
`, project, cloudName, chName, dbName1, dbName2, role1, role2)
}

func TestClickhouse(t *testing.T) {
t.Parallel()
defer recoverPanic(t)
Expand All @@ -110,7 +25,39 @@ func TestClickhouse(t *testing.T) {
dbName2 := randName("database")
roleName1 := randName("role")
roleName2 := randName("role")
yml := getClickhouseYaml(cfg.Project, cfg.PrimaryCloudName, chName, dbName1, dbName2, roleName1, roleName2)

ymlClickhouse, err := loadExampleYaml("clickhouse.yaml", map[string]string{
"google-europe-west1": cfg.PrimaryCloudName,
"my-aiven-project": cfg.Project,
"my-clickhouse": chName,
})
require.NoError(t, err)
ymlDatabase1, err := loadExampleYaml("clickhousedatabase.yaml", map[string]string{
"my-aiven-project": cfg.Project,
"my-db": dbName1,
// Remove 'databaseName' from the initial yaml
"databaseName: my-db": "",
})
require.NoError(t, err)
ymlDatabase2, err := loadExampleYaml("clickhousedatabase.yaml", map[string]string{
"my-aiven-project": cfg.Project,
"my-db": dbName2,
// Remove 'databaseName' from the initial yaml
"databaseName: my-db": "",
})
require.NoError(t, err)
ymlRole1, err := loadExampleYaml("clickhouserole.yaml", map[string]string{
"my-aiven-project": cfg.Project,
"my-role": roleName1,
})
require.NoError(t, err)
ymlRole2, err := loadExampleYaml("clickhouserole.yaml", map[string]string{
"my-aiven-project": cfg.Project,
"my-role": roleName2,
})
require.NoError(t, err)

yml := fmt.Sprintf("%s---\n%s---\n%s---\n%s---\n%s", ymlClickhouse, ymlDatabase1, ymlDatabase2, ymlRole1, ymlRole2)
s := NewSession(ctx, k8sClient, cfg.Project)

// Cleans test afterward
Expand Down Expand Up @@ -177,8 +124,10 @@ func TestClickhouse(t *testing.T) {
// Database exists
dbAvn1, err := avnClient.ClickhouseDatabase.Get(ctx, cfg.Project, chName, dbName1)
require.NoError(t, err)
assert.Equal(t, dbName1, db1.GetName())
assert.Equal(t, dbAvn1.Name, db1.GetName())

// Gets name from `metadata.name` when `databaseName` is not set
assert.Equal(t, dbName1, db1.ObjectMeta.Name)
assert.Equal(t, dbAvn1.Name, db1.ObjectMeta.Name)

// We need to validate deletion,
// because we can get false positive here:
Expand All @@ -188,8 +137,8 @@ func TestClickhouse(t *testing.T) {

dbAvn2, err := avnClient.ClickhouseDatabase.Get(ctx, cfg.Project, chName, dbName2)
require.NoError(t, err)
assert.Equal(t, dbName2, db2.GetName())
assert.Equal(t, dbAvn2.Name, db2.GetName())
assert.Equal(t, dbName2, db2.ObjectMeta.Name)
assert.Equal(t, dbAvn2.Name, db2.ObjectMeta.Name)

// Calls reconciler delete
assert.NoError(t, s.Delete(db2, func() error {
Expand All @@ -200,11 +149,11 @@ func TestClickhouse(t *testing.T) {
// Validates ClickhouseRole
role1 := new(v1alpha1.ClickhouseRole)
require.NoError(t, s.GetRunning(role1, roleName1))
assert.Equal(t, "reader", role1.Spec.Role)
assert.Equal(t, roleName1, role1.Spec.Role)

role2 := new(v1alpha1.ClickhouseRole)
require.NoError(t, s.GetRunning(role2, roleName2))
assert.Equal(t, "writer", role2.Spec.Role)
assert.Equal(t, roleName2, role2.Spec.Role)

// Roles exist
err = controllers.ClickhouseRoleExists(ctx, avnClient, role1)
Expand All @@ -226,4 +175,29 @@ func TestClickhouse(t *testing.T) {

err = controllers.ClickhouseRoleExists(ctx, avnClient, role2)
assert.ErrorContains(t, err, fmt.Sprintf("ClickhouseRole %q not found", roleName2))

// GIVEN
// Updated manifest with 'databaseName' field set
dbName3 := randName("database")
ymlDatabase3, err := loadExampleYaml("clickhousedatabase.yaml", map[string]string{
"my-aiven-project": cfg.Project,
"databaseName: my-db": fmt.Sprintf("databaseName: %s", dbName3),
})

// WHEN
// Applies updated manifest
require.NoError(t, s.Apply(ymlDatabase3))

db3 := new(v1alpha1.ClickhouseDatabase)
require.NoError(t, s.GetRunning(db3, dbName3))

dbAvn3, err := avnClient.ClickhouseDatabase.Get(ctx, cfg.Project, chName, dbName3)
require.NoError(t, err)

// THEN
// 'databaseName' field is preferred over 'metadata.name'
assert.NotEqual(t, dbName3, db3.ObjectMeta.Name)
assert.NotEqual(t, dbAvn3.Name, db3.ObjectMeta.Name)
assert.Equal(t, dbName3, db3.Spec.DatabaseName)
assert.Equal(t, dbAvn3.Name, db3.Spec.DatabaseName)
}

0 comments on commit 7ef19b3

Please sign in to comment.