Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 70 additions & 42 deletions internal/controller/clickhouse/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@ import (
"github.com/ClickHouse/clickhouse-operator/internal/controller"
"github.com/ClickHouse/clickhouse-operator/internal/controller/keeper"
"github.com/ClickHouse/clickhouse-operator/internal/controllerutil"
"github.com/ClickHouse/clickhouse-operator/internal/upgrade"
)

var (
//go:embed templates/base.yaml.tmpl
baseConfigTemplateStr string
//go:embed templates/named_collections.yaml.tmpl
namedCollectionsTemplateStr string
//go:embed templates/network.yaml.tmpl
networkConfigTemplateStr string
//go:embed templates/log_tables.yaml.tmpl
Expand All @@ -33,11 +36,41 @@ var (
)

func init() {
templateFuncs := template.FuncMap{
"yaml": func(v any) (string, error) {
data, err := yaml.Marshal(v)
return string(data), err
},
"indent": func(countRaw any, strRaw any) (string, error) {
count, ok := countRaw.(int)
if !ok {
return "", fmt.Errorf("indent: expected int for indentation value, got %T", countRaw)
}

str, ok := strRaw.(string)
if !ok {
return "", fmt.Errorf("indent: expected string for content value, got %T", strRaw)
}

builder := strings.Builder{}
indentation := strings.Repeat(" ", count)

for line := range strings.SplitSeq(str, "\n") {
if _, err := fmt.Fprintf(&builder, "%s%s\n", indentation, line); err != nil {
return "", fmt.Errorf("failed to write indented line: %w", err)
}
}

return builder.String(), nil
},
}

for _, templateSpec := range []struct {
Path string
Filename string
Raw string
Generator configGeneratorFunc
Enabled func(r *clickhouseReconciler) bool
}{{
Path: ConfigPath,
Filename: ConfigFileName,
Expand All @@ -53,6 +86,14 @@ func init() {
Filename: "00-logs-tables.yaml",
Raw: logTablesConfigTemplateStr,
Generator: logTablesConfigGenerator,
}, {
Path: path.Join(ConfigPath, ConfigDPath),
Filename: "00-named-collections.yaml",
Raw: namedCollectionsTemplateStr,
Generator: namedCollectionsConfigGenerator,
Enabled: func(r *clickhouseReconciler) bool {
return upgrade.VersionAtLeast(r.Cluster.Status.Version, minVersionNamedCollections)
},
}, {
Path: ConfigPath,
Filename: UsersFileName,
Expand All @@ -64,68 +105,35 @@ func init() {
Raw: clientConfigTemplateStr,
Generator: clientConfigGenerator,
}} {
tmpl := template.New("").Funcs(template.FuncMap{
"yaml": func(v any) (string, error) {
data, err := yaml.Marshal(v)
return string(data), err
},
"indent": func(countRaw any, strRaw any) (string, error) {
count, ok := countRaw.(int)
if !ok {
return "", fmt.Errorf("indent: expected int for indentation value, got %T", countRaw)
}

str, ok := strRaw.(string)
if !ok {
return "", fmt.Errorf("indent: expected string for content value, got %T", strRaw)
}

builder := strings.Builder{}
indentation := strings.Repeat(" ", count)

for line := range strings.SplitSeq(str, "\n") {
if _, err := fmt.Fprintf(&builder, "%s%s\n", indentation, line); err != nil {
return "", fmt.Errorf("failed to write indented line: %w", err)
}
}

return builder.String(), nil
},
})
if _, err := tmpl.Parse(templateSpec.Raw); err != nil {
panic(fmt.Sprintf("failed to parse template %s: %v", templateSpec.Filename, err))
}
tmpl := template.Must(template.New("").Funcs(templateFuncs).Parse(templateSpec.Raw))

generators = append(generators, &templateConfigGenerator{
filename: templateSpec.Filename,
path: templateSpec.Path,
template: tmpl,
generator: templateSpec.Generator,
enabled: templateSpec.Enabled,
})
}

generators = append(generators,
&extraConfigGenerator{
Name: ExtraConfigFileName,
ConfigSubPath: ConfigDPath,
Getter: func(r *clickhouseReconciler) []byte {
return r.Cluster.Spec.Settings.ExtraConfig.Raw
},
Getter: func(r *clickhouseReconciler) []byte { return r.Cluster.Spec.Settings.ExtraConfig.Raw },
},
&extraConfigGenerator{
Name: ExtraUsersConfigFileName,
ConfigSubPath: UsersDPath,
Getter: func(r *clickhouseReconciler) []byte {
return r.Cluster.Spec.Settings.ExtraUsersConfig.Raw
},
Getter: func(r *clickhouseReconciler) []byte { return r.Cluster.Spec.Settings.ExtraUsersConfig.Raw },
})
}

type configGenerator interface {
Filename() string
Path() string
ConfigKey() string
Exists(r *clickhouseReconciler) bool
Enabled(r *clickhouseReconciler) bool
Generate(r *clickhouseReconciler, id v1.ClickHouseReplicaID) (string, error)
}

Expand All @@ -134,6 +142,7 @@ type templateConfigGenerator struct {
path string
template *template.Template
generator configGeneratorFunc
enabled func(r *clickhouseReconciler) bool
}

func (g *templateConfigGenerator) Filename() string {
Expand All @@ -148,8 +157,8 @@ func (g *templateConfigGenerator) ConfigKey() string {
return controllerutil.PathToName(path.Join(g.path, g.filename))
}

func (g *templateConfigGenerator) Exists(*clickhouseReconciler) bool {
return true
func (g *templateConfigGenerator) Enabled(r *clickhouseReconciler) bool {
return g.enabled == nil || g.enabled(r)
}

func (g *templateConfigGenerator) Generate(r *clickhouseReconciler, id v1.ClickHouseReplicaID) (string, error) {
Expand Down Expand Up @@ -389,6 +398,25 @@ func clientConfigGenerator(tmpl *template.Template, r *clickhouseReconciler, _ v
return builder.String(), nil
}

type namedCollectionsConfigParams struct {
NamedCollectionsKeyEnv string
NamedCollectionsPath string
}

func namedCollectionsConfigGenerator(tmpl *template.Template, _ *clickhouseReconciler, _ v1.ClickHouseReplicaID) (string, error) {
params := namedCollectionsConfigParams{
NamedCollectionsKeyEnv: EnvNamedCollectionsKey,
NamedCollectionsPath: KeeperPathNamedCollections,
}

builder := strings.Builder{}
if err := tmpl.Execute(&builder, params); err != nil {
return "", fmt.Errorf("template named collections config: %w", err)
}

return builder.String(), nil
}

type extraConfigGenerator struct {
Name string
ConfigSubPath string
Expand All @@ -407,12 +435,12 @@ func (g *extraConfigGenerator) ConfigKey() string {
return g.Name
}

func (g *extraConfigGenerator) Exists(r *clickhouseReconciler) bool {
func (g *extraConfigGenerator) Enabled(r *clickhouseReconciler) bool {
return len(g.Getter(r)) > 0
}

func (g *extraConfigGenerator) Generate(r *clickhouseReconciler, _ v1.ClickHouseReplicaID) (string, error) {
if !g.Exists(r) {
if !g.Enabled(r) {
return "", errors.New("extra config generator called, but no extra config provided")
}

Expand Down
5 changes: 4 additions & 1 deletion internal/controller/clickhouse/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ var _ = Describe("ConfigGenerator", func() {
},
},
},
Status: v1.ClickHouseClusterStatus{
Version: "25.12.1.1",
},
},
keeper: v1.KeeperCluster{
Spec: v1.KeeperClusterSpec{
Expand All @@ -39,7 +42,7 @@ var _ = Describe("ConfigGenerator", func() {

for _, generator := range generators {
It("should generate config: "+generator.Filename(), func() {
Expect(generator.Exists(&ctx)).To(BeTrue())
Expect(generator.Enabled(&ctx)).To(BeTrue())
data, err := generator.Generate(&ctx, v1.ClickHouseReplicaID{})
Expect(err).ToNot(HaveOccurred())

Expand Down
69 changes: 52 additions & 17 deletions internal/controller/clickhouse/constants.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package clickhouse

import (
"fmt"

"github.com/blang/semver/v4"

v1 "github.com/ClickHouse/clickhouse-operator/api/v1alpha1"
"github.com/ClickHouse/clickhouse-operator/internal/controllerutil"
"github.com/ClickHouse/clickhouse-operator/internal/upgrade"
)

const (
Expand Down Expand Up @@ -32,10 +38,11 @@ const (

LogPath = "/var/log/clickhouse-server/"

DefaultClusterName = "default"
KeeperPathUsers = "/clickhouse/access"
KeeperPathUDF = "/clickhouse/user_defined"
KeeperPathDistributedDDL = "/clickhouse/task_queue/ddl"
DefaultClusterName = "default"
KeeperPathUsers = "/clickhouse/access"
KeeperPathUDF = "/clickhouse/user_defined"
KeeperPathDistributedDDL = "/clickhouse/task_queue/ddl"
KeeperPathNamedCollections = "/clickhouse/named_collections"

ContainerName = "clickhouse-server"
DefaultRevisionHistory = 10
Expand All @@ -49,27 +56,55 @@ const (
EnvDefaultUserPassword = "CLICKHOUSE_DEFAULT_USER_PASSWORD"
EnvKeeperIdentity = "CLICKHOUSE_KEEPER_IDENTITY"
EnvClusterSecret = "CLICKHOUSE_CLUSTER_SECRET"
EnvNamedCollectionsKey = "CLICKHOUSE_NAMED_COLLECTIONS_KEY"

SecretKeyInterserverPassword = "interserver-password"
SecretKeyManagementPassword = "management-password"
SecretKeyKeeperIdentity = "keeper-identity"
SecretKeyClusterSecret = "cluster-secret"
SecretKeyNamedCollectionsKey = "named-collections-key"

// NamedCollectionsKeyByteLen is the AES-128 key size in bytes (16 bytes = 32 hex chars).
NamedCollectionsKeyByteLen = 16
)

type secretSpec struct {
Key string
Env string
Format string
Generate func() any
Enabled func(cluster *v1.ClickHouseCluster) bool
}

func (s *secretSpec) generate() []byte {
var arg any
if s.Generate != nil {
arg = s.Generate()
} else {
arg = controllerutil.GeneratePassword()
}

return fmt.Appendf(nil, s.Format, arg)
}

func (s *secretSpec) enabled(cluster *v1.ClickHouseCluster) bool {
return s.Enabled == nil || s.Enabled(cluster)
}

var (
// minVersionNamedCollections is the minimum ClickHouse version that supports keeper_encrypted for named collections.
minVersionNamedCollections = upgrade.ClickHouseVersion{Major: 25, Minor: 12} //nolint:mnd
breakingStatefulSetVersion, _ = semver.Parse("0.0.1")
secretsToGenerate = map[string]string{
SecretKeyInterserverPassword: "%s",
SecretKeyManagementPassword: "%s",
SecretKeyKeeperIdentity: "clickhouse:%s",
SecretKeyClusterSecret: "%s",
}
secretsToEnvMapping = []struct {
Key string
Env string
}{
{Key: SecretKeyInterserverPassword, Env: EnvInterserverPassword},
{Key: SecretKeyKeeperIdentity, Env: EnvKeeperIdentity},
{Key: SecretKeyClusterSecret, Env: EnvClusterSecret},
clusterSecrets = []secretSpec{
{Key: SecretKeyInterserverPassword, Env: EnvInterserverPassword, Format: "%s"},
{Key: SecretKeyManagementPassword, Format: "%s"},
{Key: SecretKeyKeeperIdentity, Env: EnvKeeperIdentity, Format: "clickhouse:%s"},
{Key: SecretKeyClusterSecret, Env: EnvClusterSecret, Format: "%s"},
{Key: SecretKeyNamedCollectionsKey, Env: EnvNamedCollectionsKey, Format: "%x",
Generate: func() any { return controllerutil.GenerateRandomBytes(NamedCollectionsKeyByteLen) },
Enabled: func(cluster *v1.ClickHouseCluster) bool {
return upgrade.VersionAtLeast(cluster.Status.Version, minVersionNamedCollections)
},
},
}
)
Loading
Loading