-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathconfig.go
168 lines (139 loc) · 3.82 KB
/
config.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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// Package config defines a configuration engine based on JavaScript. A
// configuration is built from a set of JavaScript source files and executed
// to generate a state object, which is provided to components such as the
// reconfigurer for resolving state changes. JavaScript is used so certain
// common expressions can be re-used, or targets can be conditionally resolved
// based on input variables such as the machine's hostname.
package config
import (
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/pkg/errors"
"github.com/robertkrimen/otto"
"github.com/picostack/pico/task"
)
// State represents a desired system state
type State struct {
Targets task.Targets `json:"targets"`
AuthMethods []AuthMethod `json:"auths"`
Env map[string]string `json:"env"`
}
// AuthMethod represents a method of authentication for a target
type AuthMethod struct {
Name string `json:"name"` // name of the auth method
Path string `json:"path"` // path within the secret store
UserKey string `json:"user_key"` // key for username
PassKey string `json:"pass_key"` // key for password
}
// ConfigFromDirectory searches a directory for configuration files and
// constructs a desired state from the declarations.
func ConfigFromDirectory(dir, hostname string) (state State, err error) {
files, err := ioutil.ReadDir(dir)
if err != nil {
err = errors.Wrap(err, "failed to read config directory")
return
}
sources := []string{}
for _, file := range files {
if file.IsDir() {
continue
}
if filepath.Ext(file.Name()) == ".js" {
sources = append(sources, fileToString(filepath.Join(dir, file.Name())))
}
}
cb := configBuilder{
vm: otto.New(),
state: new(State),
scripts: sources,
}
err = cb.construct(hostname)
if err != nil {
return
}
state = *cb.state
return
}
type configBuilder struct {
vm *otto.Otto
state *State
scripts []string
}
func (cb *configBuilder) construct(hostname string) (err error) {
//nolint:errcheck
cb.vm.Run(`'use strict';
var STATE = {
targets: [],
auths: [],
env: {}
};
function T(t) {
if(t.name === undefined) { throw "target name undefined"; }
if(t.url === undefined) { throw "target url undefined"; }
if(t.up === undefined) { throw "target up undefined"; }
// if(t.down === undefined) { }
// if(t.env) { }
// if(t.initial_run) { }
// if(t.shutdown_command) { }
STATE.targets.push(t)
}
function E(k, v) {
STATE.env[k] = v
}
function A(a) {
if(a.name === undefined) { throw "auth name undefined"; }
if(a.path === undefined) { throw "auth path undefined"; }
if(a.user_key === undefined) { throw "auth user_key undefined"; }
if(a.pass_key === undefined) { throw "auth pass_key undefined"; }
STATE.auths.push(a);
return a.name;
}
`)
cb.vm.Set("HOSTNAME", hostname) //nolint:errcheck
env := make(map[string]string)
for _, kv := range os.Environ() {
d := strings.IndexRune(kv, '=')
env[kv[:d]] = kv[d+1:]
}
cb.vm.Set("ENV", env) //nolint:errcheck
for _, s := range cb.scripts {
err = cb.applyFileTargets(s)
if err != nil {
return
}
}
stateObj, err := cb.vm.Run(`JSON.stringify(STATE)`)
if err != nil {
return errors.Wrap(err, "failed to stringify STATE object")
}
stateRaw, err := stateObj.ToString()
if err != nil {
return errors.Wrap(err, "failed to get string representation of STATE")
}
err = json.Unmarshal([]byte(stateRaw), cb.state)
for i := range cb.state.Targets {
tmpEnv := cb.state.Targets[i].Env
cb.state.Targets[i].Env = cb.state.Env
for k, v := range tmpEnv {
cb.state.Targets[i].Env[k] = v
}
}
return
}
func (cb *configBuilder) applyFileTargets(script string) (err error) {
_, err = cb.vm.Run(script)
if err != nil {
return
}
return
}
func fileToString(path string) (contents string) {
b, err := ioutil.ReadFile(path)
if err != nil {
return
}
return string(b)
}