mirror of
https://github.com/akvorado/akvorado.git
synced 2025-12-12 06:24:10 +01:00
We use go-migrate to handle migrations. Only the "up" direction is done for now. As migrations are handled inside the application, writing downgrades is not really useful as it would be needed when downgrading an app, but then we cannot downgrade. Moreover, the way the flow topics are versioned, you can downgrade without much dommage.
152 lines
3.4 KiB
Go
152 lines
3.4 KiB
Go
package clickhouse
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"io/ioutil"
|
|
"strings"
|
|
"text/template"
|
|
"time"
|
|
)
|
|
|
|
// templatedFS is a wrapper around fs.FS to automatically expand templates
|
|
type templatedFS struct {
|
|
data interface{}
|
|
base fs.FS
|
|
}
|
|
|
|
// templatedFile is a wrapper around fs.File to automatically expand templates
|
|
type templatedFile struct {
|
|
data interface{}
|
|
base fs.File
|
|
offset int
|
|
rendered []byte
|
|
}
|
|
|
|
// templatedDirEntry is a wrapper around fs.DirEntry to automatically expand templates
|
|
type templatedDirEntry struct {
|
|
base fs.DirEntry
|
|
}
|
|
|
|
// templatedFileInfo is a wrapper around fs.FileInfo to automatically expand templates
|
|
type templatedFileInfo struct {
|
|
base fs.FileInfo
|
|
}
|
|
|
|
func (tf *templatedFile) Stat() (fs.FileInfo, error) {
|
|
info, err := tf.base.Stat()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &templatedFileInfo{info}, nil
|
|
}
|
|
func (tf *templatedFile) Read(buf []byte) (int, error) {
|
|
if tf.rendered == nil {
|
|
tmpl, err := ioutil.ReadAll(tf.base)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
t, err := template.New("anything").Option("missingkey=error").Parse(string(tmpl))
|
|
if err != nil {
|
|
return 0, fmt.Errorf("cannot parse template: %w", err)
|
|
}
|
|
b := bytes.NewBuffer([]byte{})
|
|
if err := t.Execute(b, tf.data); err != nil {
|
|
return 0, fmt.Errorf("cannot execute template: %w", err)
|
|
}
|
|
tf.rendered = b.Bytes()
|
|
}
|
|
if tf.offset >= len(tf.rendered) {
|
|
return 0, io.EOF
|
|
}
|
|
n := copy(buf, tf.rendered[tf.offset:])
|
|
tf.offset += n
|
|
return n, nil
|
|
}
|
|
func (tf *templatedFile) Close() error {
|
|
return tf.base.Close()
|
|
}
|
|
|
|
func (tfs *templatedFS) ReadDir(name string) ([]fs.DirEntry, error) {
|
|
entries, err := fs.ReadDir(tfs.base, name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
results := []fs.DirEntry{}
|
|
for _, entry := range entries {
|
|
if strings.HasSuffix(entry.Name(), ".tmpl") {
|
|
results = append(results, &templatedDirEntry{entry})
|
|
} else {
|
|
results = append(results, entry)
|
|
}
|
|
}
|
|
return results, nil
|
|
}
|
|
|
|
func (tde *templatedDirEntry) Name() string {
|
|
return strings.TrimSuffix(tde.base.Name(), ".tmpl")
|
|
}
|
|
func (tde *templatedDirEntry) IsDir() bool {
|
|
return tde.base.IsDir()
|
|
}
|
|
func (tde *templatedDirEntry) Type() fs.FileMode {
|
|
return tde.base.Type()
|
|
}
|
|
func (tde *templatedDirEntry) Info() (fs.FileInfo, error) {
|
|
info, err := tde.base.Info()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &templatedFileInfo{info}, nil
|
|
}
|
|
|
|
func (tfi *templatedFileInfo) Name() string {
|
|
return strings.TrimSuffix(tfi.base.Name(), ".tmpl")
|
|
}
|
|
func (tfi *templatedFileInfo) Size() int64 {
|
|
return 0 // Can't be sure
|
|
}
|
|
func (tfi *templatedFileInfo) Mode() fs.FileMode {
|
|
return tfi.base.Mode()
|
|
}
|
|
func (tfi *templatedFileInfo) ModTime() time.Time {
|
|
return tfi.base.ModTime()
|
|
}
|
|
func (tfi *templatedFileInfo) IsDir() bool {
|
|
return tfi.base.IsDir()
|
|
}
|
|
func (tfi *templatedFileInfo) Sys() interface{} {
|
|
return nil
|
|
}
|
|
|
|
func (tfs *templatedFS) Open(name string) (fs.File, error) {
|
|
candidates := []string{fmt.Sprintf("%s.tmpl", name), name}
|
|
var f fs.File
|
|
var err error
|
|
var candidate string
|
|
for _, candidate = range candidates {
|
|
f, err = tfs.base.Open(candidate)
|
|
if err != nil && errors.Is(err, fs.ErrNotExist) {
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
info, err := f.Stat()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to stat file: %w", err)
|
|
}
|
|
if info.IsDir() {
|
|
panic("assumed that Open() won't be called for a directory")
|
|
}
|
|
if !strings.HasSuffix(candidate, ".tmpl") {
|
|
return f, nil
|
|
}
|
|
return &templatedFile{data: tfs.data, base: f}, nil
|
|
}
|