Skip to content

Commit 322c3ff

Browse files
committed
feat(spec): add support for health
Add support for providing things like livenessProbe and readinessProbe. Add a new field in container level called health which has readinessProbe and livenessProbe. These fields are identical to the way we define livenessProbe or readinessProbe in kubernetes. So no new extra field has been added, everything that k8s supports works with the health option in OpenCompose. fixes redhat-developer#24
1 parent 55407c9 commit 322c3ff

File tree

9 files changed

+1261
-1
lines changed

9 files changed

+1261
-1
lines changed

examples/wordpress/health.yaml

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
version: '0.1-dev'
2+
3+
services:
4+
- name: database
5+
containers:
6+
- image: mariadb:10
7+
env:
8+
- name: MYSQL_ROOT_PASSWORD
9+
value: rootpasswd
10+
- name: MYSQL_DATABASE
11+
value: wordpress
12+
- name: MYSQL_USER
13+
value: wordpress
14+
- name: MYSQL_PASSWORD
15+
value: wordpress
16+
ports:
17+
- port: 3306
18+
mounts:
19+
- volumeRef: database
20+
mountPath: /var/lib/mysql
21+
22+
- name: web
23+
containers:
24+
- image: wordpress:4
25+
env:
26+
- name: WORDPRESS_DB_HOST
27+
value: database:3306
28+
- name: WORDPRESS_DB_PASSWORD
29+
value: wordpress
30+
- name: WORDPRESS_DB_USER
31+
value: wordpress
32+
- name: WORDPRESS_DB_NAME
33+
value: wordpress
34+
ports:
35+
- port: 80
36+
type: external
37+
health:
38+
readinessProbe:
39+
httpGet:
40+
port: 8080
41+
path: /v1
42+
initialDelaySeconds: 10
43+
44+
volumes:
45+
- name: database
46+
size: 100Mi
47+
accessMode: ReadWriteOnce

pkg/encoding/util/util.go

+20
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,23 @@ func ValidateRequiredFields(i interface{}) error {
122122

123123
return nil
124124
}
125+
126+
// Converts the yaml read into empty interface to JSONified
127+
// empty interface, this can be used to marshal into actual
128+
// JSON and from there if you want it you can read it into
129+
// actual internal objects.
130+
func InterfaceToJSON(i interface{}) interface{} {
131+
switch x := i.(type) {
132+
case map[interface{}]interface{}:
133+
m2 := map[string]interface{}{}
134+
for k, v := range x {
135+
m2[k.(string)] = InterfaceToJSON(v)
136+
}
137+
return m2
138+
case []interface{}:
139+
for i, v := range x {
140+
x[i] = InterfaceToJSON(v)
141+
}
142+
}
143+
return i
144+
}

pkg/encoding/util/util_test.go

+58
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"errors"
55
"reflect"
66
"testing"
7+
8+
"gopkg.in/yaml.v2"
79
)
810

911
type S struct {
@@ -135,3 +137,59 @@ func TestValidateRequiredFields(t *testing.T) {
135137
}
136138
}
137139
}
140+
141+
func TestInterfaceToJSON(t *testing.T) {
142+
tests := []struct {
143+
input string
144+
output map[string]interface{}
145+
}{
146+
{
147+
input: `foo: bar`,
148+
output: map[string]interface{}{"foo": "bar"},
149+
},
150+
{
151+
input: `
152+
one:
153+
- 1
154+
- 2
155+
- 3
156+
two: four`,
157+
output: map[string]interface{}{
158+
"two": "four",
159+
"one": []interface{}{1, 2, 3},
160+
},
161+
},
162+
{
163+
input: `
164+
one:
165+
two:
166+
three: four
167+
five: six
168+
seven:
169+
- eight
170+
- nine`,
171+
output: map[string]interface{}{
172+
"one": map[string]interface{}{
173+
"two": map[string]interface{}{
174+
"three": "four",
175+
},
176+
},
177+
"five": "six",
178+
"seven": []interface{}{"eight", "nine"},
179+
},
180+
},
181+
}
182+
183+
for _, test := range tests {
184+
var input interface{}
185+
err := yaml.Unmarshal([]byte(test.input), &input)
186+
if err != nil {
187+
t.Fatalf("failed unmarshalling: %v", err)
188+
}
189+
gotOutput := InterfaceToJSON(input)
190+
191+
if !reflect.DeepEqual(test.output, gotOutput) {
192+
t.Errorf("Expected: %#v\nGot: %#v", test.output, gotOutput)
193+
}
194+
}
195+
}

pkg/encoding/v1/encoding.go

+80
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package v1
22

33
import (
4+
"encoding/json"
45
"errors"
56
"fmt"
67
"strconv"
@@ -10,6 +11,8 @@ import (
1011
"github.com/redhat-developer/opencompose/pkg/goutil"
1112
"github.com/redhat-developer/opencompose/pkg/object"
1213
"gopkg.in/yaml.v2"
14+
15+
api_v1 "k8s.io/client-go/pkg/api/v1"
1316
)
1417

1518
const (
@@ -219,11 +222,83 @@ func (m *Mount) UnmarshalYAML(unmarshal func(interface{}) error) error {
219222
return nil
220223
}
221224

225+
type Health struct {
226+
// Data holder for ReadinessProbe while parsing
227+
ReadinessProbeData interface{} `yaml:"readinessProbe,omitempty"`
228+
ReadinessProbe *api_v1.Probe
229+
230+
// Data holder for LivenessProbe while parsing
231+
LivenessProbeData interface{} `yaml:"livenessProbe,omitempty"`
232+
LivenessProbe *api_v1.Probe
233+
}
234+
235+
// If given an interface which has JSONified data of type Probe
236+
// this function will read the interface and give concrete
237+
// data strcuture pointer.
238+
func interfaceToProbe(i interface{}) (*api_v1.Probe, error) {
239+
i = util.InterfaceToJSON(i)
240+
241+
var b []byte
242+
var err error
243+
if b, err = json.Marshal(i); err != nil {
244+
return nil, fmt.Errorf("error: marshalling interface to bytes: %v", err)
245+
}
246+
var p api_v1.Probe
247+
if err = json.Unmarshal(b, &p); err != nil {
248+
return nil, fmt.Errorf("error: unmarshalling bytes to Probe: %v", err)
249+
}
250+
return &p, nil
251+
}
252+
253+
func (h *Health) UnmarshalYAML(unmarshal func(interface{}) error) error {
254+
type HealthAlias Health
255+
var st struct {
256+
HealthAlias `yaml:",inline"`
257+
Leftovers map[string]interface{} `yaml:",inline"` // Catches all undefined fields and must be empty after parsing.
258+
}
259+
260+
err := unmarshal(&st)
261+
if err != nil {
262+
return err
263+
}
264+
265+
if len(st.Leftovers) > 0 {
266+
return util.NewExcessKeysErrorFromMap("Health", st.Leftovers)
267+
}
268+
269+
*h = Health(st.HealthAlias)
270+
271+
// extract the data from interface into concrete data type 'Probe'
272+
if h.ReadinessProbeData != nil {
273+
h.ReadinessProbe, err = interfaceToProbe(h.ReadinessProbeData)
274+
if err != nil {
275+
return fmt.Errorf("readinessProbe: %v", err)
276+
}
277+
h.ReadinessProbeData = interface{}(nil)
278+
}
279+
280+
// extract the data from interface into concrete data type 'Probe'
281+
if h.LivenessProbeData != nil {
282+
h.LivenessProbe, err = interfaceToProbe(h.LivenessProbeData)
283+
if err != nil {
284+
return fmt.Errorf("livenessProbe: %v", err)
285+
}
286+
h.LivenessProbeData = interface{}(nil)
287+
}
288+
289+
// TODO: Right now we have no way of finding if the excess keys are given
290+
// by the user, since we are doing the whole conversion from YAML to JSON
291+
// and then parsing it into the internal k8s structs
292+
293+
return nil
294+
}
295+
222296
type Container struct {
223297
Image ImageRef `yaml:"image"`
224298
Env []EnvVariable `yaml:"env,omitempty"`
225299
Ports []Port `yaml:"ports,omitempty"`
226300
Mounts []Mount `yaml:"mounts,omitempty"`
301+
Health *Health `yaml:"health,omitempty"`
227302
}
228303

229304
func (c *Container) UnmarshalYAML(unmarshal func(interface{}) error) error {
@@ -443,6 +518,11 @@ func (d *Decoder) Decode(data []byte) (*object.OpenCompose, error) {
443518
oc.Mounts = append(oc.Mounts, mount)
444519
}
445520

521+
if c.Health != nil {
522+
oc.Health.LivenessProbe = c.Health.LivenessProbe
523+
oc.Health.ReadinessProbe = c.Health.ReadinessProbe
524+
}
525+
446526
// convert env
447527
for _, e := range c.Env {
448528
oc.Environment = append(oc.Environment, object.EnvVariable{

0 commit comments

Comments
 (0)