/
master.go
137 lines (120 loc) · 4.84 KB
/
master.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// Copyright 2014, Surul Software Labs GmbH
// All rights reserved.
package ghostgres
import (
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"reflect"
"regexp"
)
const templateDir = "testdata/template"
var pgBinDir = flag.String("ghostgres_pg_bin_dir", "", "Directory containing PostgreSQL binaries")
var defaultName = flag.String("ghostgres_template", "default", "The value for the default template database")
func postgresBinary() string { return filepath.Join(*pgBinDir, "postgres") }
func parseVersion(output string) (version string) {
version = regexp.MustCompile("[0-9]+\\.[0-9]+\\.[0-9]+").FindString(output)
check.True(version != "", fmt.Sprintf("failed to parse postgres version from %s", output))
return
}
func postgresVersion() (version string) {
return parseVersion(string(check.Return(exec.Command(postgresBinary(), "--version").Output()).([]byte)))
}
type ghostgresTemplate string
var gopathFn = func() string { return os.Getenv("GOPATH") }
func newTemplate(root, name string) ghostgresTemplate {
if root == DefaultTemplateDir {
gopath := gopathFn()
check.True(gopath != "", "GOPATH is not set. Unable to locate templates")
// Use reflection to determine the package path so we're safe from package
// relocations.
pkgPath := filepath.Join(gopath, filepath.Join("src", reflect.TypeOf(PostgresCluster{}).PkgPath()))
root = filepath.Join(pkgPath, templateDir)
}
if name == DefaultTemplate {
name = *defaultName
}
return ghostgresTemplate(filepath.Join(root, filepath.Join(name, filepath.Join(postgresVersion()))))
}
func (t ghostgresTemplate) path() string { return string(t) }
func (t ghostgresTemplate) data() string { return filepath.Join(t.path(), "data") }
func (t ghostgresTemplate) config() string { return filepath.Join(t.path(), "ghostgres.json") }
func (t ghostgresTemplate) exists() bool {
_, err := os.Stat(t.config())
return err == nil
}
func (t ghostgresTemplate) clone(cloneDir string) *PostgresCluster {
cluster := PostgresCluster{}
check.Error(json.Unmarshal(check.Return(ioutil.ReadFile(t.config())).([]byte), &cluster))
var onStop func()
if cloneDir == "" {
tempDir := check.Return(ioutil.TempDir("", "ghostgres_clone")).(string)
cloneDir = filepath.Join(tempDir, "clone")
onStop = func() { os.RemoveAll(tempDir) }
}
cloned := check.Return(cluster.Clone(cloneDir)).(*PostgresCluster)
cloned.onStop = onStop
return cloned
}
func (t ghostgresTemplate) createFrom(c *PostgresCluster) (err error) {
check.True(!c.Running(), "cannot create a template from a running cluster")
check.Error(os.MkdirAll(t.path(), 0700))
clone := check.Return(c.Clone(t.data())).(*PostgresCluster)
marshalled := check.Return(json.MarshalIndent(clone, "", " ")).([]byte)
return ioutil.WriteFile(t.config(), marshalled, 0600)
}
// DefaultTemplateDir is a convenience value used to refer to the
// installed location of the ghostgres package. It is to be used as the
// root location if you would like to have ghostgres manage all template
// copies.
const DefaultTemplateDir = ""
// DefaultTemplate is a convenience value used to refer to a default
// template. If used the value of the --ghostgres_template flag will
// be used as the template name.
const DefaultTemplate = ""
// FromDefault is equivalent to FromTemplate(DefaultTemplateDir, DefaultTemplate, dest)
func FromDefault(dest string) (p *PostgresCluster, err error) {
return FromTemplate(DefaultTemplateDir, DefaultTemplate, dest)
}
// FromTemplate will attempt to clone a cluster from a template located at
//
// %dir%/%name%/%pg_version%/
//
// where dir and name have the same behaviour as in Freeze(dir,name).
//
// If the defaults don't exist an error will be returned. Please call
// Freeze(dir, name) first before calling FromTemplate.
//
// If dest is empty a temporary directory is created for the clone and will
// be deleted when Stop() is called on the cluster.
func FromTemplate(dir, name, dest string) (p *PostgresCluster, err error) {
defer check.Recover(&err)
return newTemplate(dir, name).clone(dest), nil
}
// Freeze will save a template to
//
// %dir%/%name%/%pg_version%/
//
// where
// %dir% directory into which to freeze. This will
// create a copy of the cluster into %dir%/data
// If %dir% is empty <path_to_ghostgres>/testdata/template is used.
// %name% is the value of the parameter 'name'. If empty the value of
// the ghostgres_template flag is used.
// %pg_version% is the result of calling PostgresVersion()
//
// If a frozen template exists it will return an error
func (cluster *PostgresCluster) Freeze(dir, name string) (err error) {
defer check.Recover(&err)
return newTemplate(dir, name).createFrom(cluster)
}
// Delete will delete a saved template configuration. dir and name
// have the same behaviour as in Freeze.
func Delete(dir, name string) (err error) {
defer check.Recover(&err)
return os.RemoveAll(newTemplate(dir, name).path())
}