cmd/clickhouse/entrypoint.go (173 lines of code) (raw):

package main import ( "context" _ "embed" "errors" "fmt" "log" "os" "os/exec" "path/filepath" "strings" "syscall" "time" "github.com/Altinity/clickhouse-backup/pkg/status" clickhousebackup "github.com/JetBrains/ij-perf-report-aggregator/pkg/clickhouse-backup" "github.com/nats-io/nats.go" "go.deanishe.net/env" ) //go:embed config.xml var clickhouseConfig []byte func main() { clickhouseExecutable := "/usr/bin/clickhouse" isLocalRun := os.Getenv("KUBERNETES_SERVICE_HOST") == "" ctx := context.Background() if isLocalRun { clickhouseExecutable = "/Users/maxim.kolmakov/clickhouse" clickhousebackup.SetS3EnvForLocalRun(ctx) } bucket := getEnvOrFile("S3_BUCKET", "/etc/s3/bucket") s3AccessKey := getEnvOrFile("S3_ACCESS_KEY", "/etc/s3/accessKey") s3SecretKey := getEnvOrFile("S3_SECRET_KEY", "/etc/s3/secretKey") restoreData := os.Getenv("RESTORE_DB") == "true" configFile := "/var/lib/clickhouse/config.xml" if isLocalRun { workingDir, err := os.Getwd() if err != nil { log.Fatal(err) } configFile = filepath.Join(workingDir, "deployment", "ch-local-config.xml") } if restoreData { err := prepareConfigAndDir(isLocalRun, bucket, s3AccessKey, s3SecretKey, configFile) if err != nil { log.Fatal(err) } } cmd := exec.CommandContext(ctx, clickhouseExecutable, "server", "--config-file="+configFile) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err := cmd.Start() if err != nil { log.Fatal(err) } if restoreData { defer func() { err = cmd.Process.Signal(syscall.SIGTERM) if err != nil { log.Println(err) _ = cmd.Process.Kill() } err = cmd.Wait() if err != nil { log.Println(err) } }() err = restoreDb() if err != nil { log.Fatal(err) } return } go func() { // wait for clickhouse server start time.Sleep(10 * time.Second) requestClearCache() }() err = cmd.Wait() if err != nil { log.Fatal(err) } } func prepareConfigAndDir(isLocalRun bool, bucket string, s3AccessKey string, s3SecretKey string, configFile string) error { chDir := "/var/lib/clickhouse" if isLocalRun { chDir = env.GetString("CLICKHOUSE_DATA_PATH", "/Volumes/data/ij-perf-db/clickhouse") } entries, err := os.ReadDir(chDir) if err != nil && !os.IsNotExist(err) { return err } for _, entry := range entries { err = os.RemoveAll(filepath.Join(chDir, entry.Name())) if err != nil && !os.IsNotExist(err) { return err } } if !isLocalRun { s3Url := "https://" + bucket + ".s3.eu-west-1.amazonaws.com/data/" log.Print("S3 URL: " + s3Url) s := strings.NewReplacer( "$S3_URL", s3Url, "$S3_ACCESS_KEY", s3AccessKey, "$S3_SECRET_KEY", s3SecretKey, ).Replace(string(clickhouseConfig)) // /etc is not writeable err = os.WriteFile(configFile, []byte(s), 0o666) if err != nil { return err } } return nil } func restoreDb() error { // wait a little bit for clickhouse start time.Sleep(4 * time.Second) backuper := clickhousebackup.CreateBackuper() attemptCount := 3 var backupName string main: for i := range attemptCount { backups, err := backuper.GetRemoteBackups(context.Background(), true) if err != nil { if i < attemptCount { time.Sleep(time.Duration((i+1)*3) * time.Second) continue } return fmt.Errorf("%w", err) } if len(backups) != 0 { for j := len(backups) - 1; j > 0; j-- { if backups[j].Broken == "" { backupName = backups[j].BackupName break main } } } } if backupName == "" { return errors.New("no remote backup") } err := backuper.RestoreFromRemote(backupName, "", nil, nil, false, false, false, false, false, false, false, status.NotFromAPI) if err != nil { return fmt.Errorf("%w", err) } log.Println("DB is restored (backup=" + backupName + ")") return nil } func requestClearCache() { url := os.Getenv("NATS") if url == "" { url = "nats://nats:4222" } nc, err := nats.Connect(url, nats.Name("NATS Sample Publisher")) if err != nil { log.Fatal(err) } defer nc.Close() err = nc.Publish("server.clearCache", []byte("clickhouse")) if err != nil { log.Fatal(err) } err = nc.Flush() if err != nil { log.Fatal(err) } } func getEnvOrFile(envName string, file string) string { v := os.Getenv(envName) if v == "" { b, err := os.ReadFile(file) if err != nil { log.Fatal(err) } return string(b) } return v }