-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathbosh_template_go.go
150 lines (130 loc) · 4.92 KB
/
bosh_template_go.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
//go:generate rice embed-go
package boshgotemplate
import (
"io/ioutil"
"os"
"os/exec"
"path/filepath"
rice "github.com/GeertJohan/go.rice"
"github.com/pkg/errors"
yaml "gopkg.in/yaml.v2"
)
const (
rbClassFileName = "bosh_erb_renderer.rb"
evaluationContextYAMLFileName = "evaluation_context.yaml"
instanceInfoYAMLFileName = "instance_info.yaml"
requiredVersion = ">=2.2.1"
)
var (
// RubyBinary is the name of the ruby binary. Can be an absolute path.
RubyBinary = "ruby"
// RubyGemBinary is the name of the ruby gem binary. Can be an absolute path.
RubyGemBinary = "gem"
)
// EvaluationContext is the context passed to the erb renderer
type EvaluationContext struct {
Properties map[string]interface{} `yaml:"properties"`
}
// InstanceInfo represents instance group runtime information
type InstanceInfo struct {
Address string `yaml:"address"`
AZ string `yaml:"az"`
Bootstrap bool `yaml:"bootstrap"`
Deployment string `yaml:"deployment"`
ID string `yaml:"id"`
Index int `yaml:"index"`
IP string `yaml:"ip"`
Name string `yaml:"name"`
}
// ERBRenderer represents a BOSH Job erb template renderer
type ERBRenderer struct {
EvaluationContext *EvaluationContext
InstanceInfo *InstanceInfo
JobSpecFilePath string
}
// NewERBRenderer creates a new ERBRenderer with an EvaluationContext
func NewERBRenderer(evaluationContext *EvaluationContext, instanceInfo *InstanceInfo, jobSpecFilePath string) *ERBRenderer {
return &ERBRenderer{
EvaluationContext: evaluationContext,
JobSpecFilePath: jobSpecFilePath,
InstanceInfo: instanceInfo,
}
}
// Render renders an erb file using an EvaluationContext
func (e *ERBRenderer) Render(inputFilePath, outputFilePath string) (returnErr error) {
// Create a temporary work directory
tmpDir, err := ioutil.TempDir("", "bosh-erb-renderer")
if err != nil {
return errors.Wrap(err, "failed to create temporary directory in erb renderer")
}
defer func() {
if err = os.RemoveAll(tmpDir); err != nil {
returnErr = errors.Wrap(err, "failed to cleanup erb renderer temporary directory")
}
}()
// Write the ruby class to a file
rbClassFilePath := filepath.Join(tmpDir, rbClassFileName)
templateEvaluationContextRb, err := rice.
MustFindBox("rb").
Bytes("template_evaluation_context.rb")
if err != nil {
return errors.Wrap(err, "failed to load ruby class")
}
err = ioutil.WriteFile(rbClassFilePath, templateEvaluationContextRb, 0600)
if err != nil {
return errors.Wrap(err, "failed to write the rendering ruby class file")
}
// Marshal the evaluation context
evalContextBytes, err := yaml.Marshal(e.EvaluationContext)
if err != nil {
return errors.Wrap(err, "failed to marshal the evaluation context")
}
evaluationContextYAMLFilePath := filepath.Join(tmpDir, evaluationContextYAMLFileName)
err = ioutil.WriteFile(evaluationContextYAMLFilePath, evalContextBytes, 0600)
if err != nil {
return errors.Wrap(err, "failed to write the evaluation context yaml file")
}
// Marshal instance information
instanceInfoBytes, err := yaml.Marshal(e.InstanceInfo)
if err != nil {
return errors.Wrap(err, "failed to marshal instance runtime information")
}
instanceInfoYAMLFilePath := filepath.Join(tmpDir, instanceInfoYAMLFileName)
err = ioutil.WriteFile(instanceInfoYAMLFilePath, instanceInfoBytes, 0600)
if err != nil {
return errors.Wrap(err, "failed to write instance runtime information yaml file")
}
// Run rendering
err = run(rbClassFilePath, evaluationContextYAMLFilePath, e.JobSpecFilePath, instanceInfoYAMLFilePath, inputFilePath, outputFilePath)
if err != nil {
return errors.Wrap(err, "failed to render template")
}
return nil
}
func run(rubyClassFilePath, evaluationContextYAMLFilePath, jobSpecFilePath, instanceInfoYAMLFilePath, inputFilePath, outputFilePath string) error {
cmd := exec.Command(RubyBinary, rubyClassFilePath, evaluationContextYAMLFilePath, jobSpecFilePath, instanceInfoYAMLFilePath, inputFilePath, outputFilePath)
outputBytes, err := cmd.CombinedOutput()
if err != nil {
output := string(outputBytes)
return errors.Wrapf(err, "rendering failed: %s", output)
}
return nil
}
// CheckRubyAvailable can be used by callers to verify that ruby is correctly installed
func CheckRubyAvailable() error {
_, err := exec.LookPath(RubyBinary)
if err != nil {
return errors.Wrap(err, "rendering BOSH templates requires ruby, please install ruby and make sure it's in your PATH")
}
return nil
}
// CheckBOSHTemplateGemAvailable can be used by callers to verify that the bosh template gem is correctly installed
func CheckBOSHTemplateGemAvailable() error {
cmd := exec.Command(RubyGemBinary, "list", "-i", "bosh-template", "-v", requiredVersion)
outputBytes, err := cmd.CombinedOutput()
if err != nil {
output := string(outputBytes)
return errors.Wrapf(err, "rendering BOSH templates requires the bosh-template(%s) ruby gem, please install it: %s ", requiredVersion, output)
}
return nil
}