Skip to content

Commit f37d49c

Browse files
committed
Load jobs from yaml with compose-go pipeline
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
1 parent 93ec08a commit f37d49c

79 files changed

Lines changed: 1265 additions & 205 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

loader/environment.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,27 +25,32 @@ import (
2525
// ResolveEnvironment update the environment variables for the format {- VAR} (without interpolation)
2626
func ResolveEnvironment(dict map[string]any, environment types.Mapping) {
2727
resolveServicesEnvironment(dict, environment)
28+
resolveContainerEnvironment(dict, "jobs", environment)
2829
resolveSecretsEnvironment(dict, environment)
2930
resolveConfigsEnvironment(dict, environment)
3031
}
3132

3233
func resolveServicesEnvironment(dict map[string]any, environment types.Mapping) {
33-
services, ok := dict["services"].(map[string]any)
34+
resolveContainerEnvironment(dict, "services", environment)
35+
}
36+
37+
func resolveContainerEnvironment(dict map[string]any, key string, environment types.Mapping) {
38+
containers, ok := dict[key].(map[string]any)
3439
if !ok {
3540
return
3641
}
3742

38-
for service, cfg := range services {
39-
serviceConfig, ok := cfg.(map[string]any)
43+
for name, cfg := range containers {
44+
config, ok := cfg.(map[string]any)
4045
if !ok {
4146
continue
4247
}
43-
serviceEnv, ok := serviceConfig["environment"].([]any)
48+
envList, ok := config["environment"].([]any)
4449
if !ok {
4550
continue
4651
}
4752
envs := []any{}
48-
for _, env := range serviceEnv {
53+
for _, env := range envList {
4954
varEnv, ok := env.(string)
5055
if !ok {
5156
continue
@@ -57,10 +62,10 @@ func resolveServicesEnvironment(dict map[string]any, environment types.Mapping)
5762
envs = append(envs, varEnv)
5863
}
5964
}
60-
serviceConfig["environment"] = envs
61-
services[service] = serviceConfig
65+
config["environment"] = envs
66+
containers[name] = config
6267
}
63-
dict["services"] = services
68+
dict[key] = containers
6469
}
6570

6671
func resolveSecretsEnvironment(dict map[string]any, environment types.Mapping) {

loader/extends.go

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,24 @@ import (
2828
)
2929

3030
func ApplyExtends(ctx context.Context, dict map[string]any, opts *Options, tracker *cycleTracker, post PostProcessor) error {
31-
a, ok := dict["services"]
32-
if !ok {
33-
return nil
34-
}
35-
services, ok := a.(map[string]any)
36-
if !ok {
37-
return fmt.Errorf("services must be a mapping")
38-
}
39-
for name := range services {
40-
merged, err := applyServiceExtends(ctx, name, services, opts, tracker, post)
41-
if err != nil {
42-
return err
31+
for _, key := range []string{"services", "jobs"} {
32+
a, ok := dict[key]
33+
if !ok {
34+
continue
35+
}
36+
entries, ok := a.(map[string]any)
37+
if !ok {
38+
return fmt.Errorf("%s must be a mapping", key)
39+
}
40+
for name := range entries {
41+
merged, err := applyServiceExtends(ctx, name, entries, opts, tracker, post)
42+
if err != nil {
43+
return err
44+
}
45+
entries[name] = merged
4346
}
44-
services[name] = merged
47+
dict[key] = entries
4548
}
46-
dict["services"] = services
4749
return nil
4850
}
4951

loader/include.go

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -166,23 +166,10 @@ func ApplyInclude(ctx context.Context, workingDir string, environment types.Mapp
166166

167167
// importResources import into model all resources defined by imported, and report error on conflict
168168
func importResources(source map[string]any, target map[string]any, processor PostProcessor) error {
169-
if err := importResource(source, target, "services", processor); err != nil {
170-
return err
171-
}
172-
if err := importResource(source, target, "volumes", processor); err != nil {
173-
return err
174-
}
175-
if err := importResource(source, target, "networks", processor); err != nil {
176-
return err
177-
}
178-
if err := importResource(source, target, "secrets", processor); err != nil {
179-
return err
180-
}
181-
if err := importResource(source, target, "configs", processor); err != nil {
182-
return err
183-
}
184-
if err := importResource(source, target, "models", processor); err != nil {
185-
return err
169+
for _, key := range []string{"services", "jobs", "volumes", "networks", "secrets", "configs", "models"} {
170+
if err := importResource(source, target, key, processor); err != nil {
171+
return err
172+
}
186173
}
187174
return nil
188175
}

loader/loader.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -787,9 +787,9 @@ func Transform(source interface{}, target interface{}) error {
787787
return decoder.Decode(source)
788788
}
789789

790-
// nameServices create implicit `name` key for convenience accessing service
790+
// nameServices create implicit `name` key for convenience accessing service or job
791791
func nameServices(from reflect.Value, to reflect.Value) (interface{}, error) {
792-
if to.Type() == reflect.TypeOf(types.Services{}) {
792+
if to.Type() == reflect.TypeOf(types.Services{}) || to.Type() == reflect.TypeOf(types.Jobs{}) {
793793
nameK := reflect.ValueOf("name")
794794
iter := from.MapRange()
795795
for iter.Next() {

loader/normalize.go

Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -29,21 +29,25 @@ import (
2929
func Normalize(dict map[string]any, env types.Mapping) (map[string]any, error) {
3030
normalizeNetworks(dict)
3131

32-
if d, ok := dict["services"]; ok {
33-
services := d.(map[string]any)
34-
for name, s := range services {
35-
service := s.(map[string]any)
32+
for _, key := range []string{"services", "jobs"} {
33+
d, ok := dict[key]
34+
if !ok {
35+
continue
36+
}
37+
containers := d.(map[string]any)
38+
for name, s := range containers {
39+
container := s.(map[string]any)
3640

37-
if service["pull_policy"] == types.PullPolicyIfNotPresent {
38-
service["pull_policy"] = types.PullPolicyMissing
41+
if container["pull_policy"] == types.PullPolicyIfNotPresent {
42+
container["pull_policy"] = types.PullPolicyMissing
3943
}
4044

4145
fn := func(s string) (string, bool) {
4246
v, ok := env[s]
4347
return v, ok
4448
}
4549

46-
if b, ok := service["build"]; ok {
50+
if b, ok := container["build"]; ok {
4751
build := b.(map[string]any)
4852
if build["context"] == nil {
4953
build["context"] = "."
@@ -56,20 +60,20 @@ func Normalize(dict map[string]any, env types.Mapping) (map[string]any, error) {
5660
build["args"], _ = resolve(a, fn, false)
5761
}
5862

59-
service["build"] = build
63+
container["build"] = build
6064
}
6165

62-
if e, ok := service["environment"]; ok {
63-
service["environment"], _ = resolve(e, fn, true)
66+
if e, ok := container["environment"]; ok {
67+
container["environment"], _ = resolve(e, fn, true)
6468
}
6569

6670
var dependsOn map[string]any
67-
if d, ok := service["depends_on"]; ok {
71+
if d, ok := container["depends_on"]; ok {
6872
dependsOn = d.(map[string]any)
6973
} else {
7074
dependsOn = map[string]any{}
7175
}
72-
if l, ok := service["links"]; ok {
76+
if l, ok := container["links"]; ok {
7377
links := l.([]any)
7478
for _, e := range links {
7579
link := e.(string)
@@ -88,7 +92,7 @@ func Normalize(dict map[string]any, env types.Mapping) (map[string]any, error) {
8892
}
8993

9094
for _, namespace := range []string{"network_mode", "ipc", "pid", "uts", "cgroup"} {
91-
if n, ok := service[namespace]; ok {
95+
if n, ok := container[namespace]; ok {
9296
ref := n.(string)
9397
if strings.HasPrefix(ref, types.ServicePrefix) {
9498
shared := ref[len(types.ServicePrefix):]
@@ -103,18 +107,18 @@ func Normalize(dict map[string]any, env types.Mapping) (map[string]any, error) {
103107
}
104108
}
105109

106-
if v, ok := service["volumes"]; ok {
110+
if v, ok := container["volumes"]; ok {
107111
volumes := v.([]any)
108112
for i, volume := range volumes {
109113
vol := volume.(map[string]any)
110114
target := vol["target"].(string)
111115
vol["target"] = path.Clean(target)
112116
volumes[i] = vol
113117
}
114-
service["volumes"] = volumes
118+
container["volumes"] = volumes
115119
}
116120

117-
if n, ok := service["volumes_from"]; ok {
121+
if n, ok := container["volumes_from"]; ok {
118122
volumesFrom := n.([]any)
119123
for _, v := range volumesFrom {
120124
vol := v.(string)
@@ -131,12 +135,12 @@ func Normalize(dict map[string]any, env types.Mapping) (map[string]any, error) {
131135
}
132136
}
133137
if len(dependsOn) > 0 {
134-
service["depends_on"] = dependsOn
138+
container["depends_on"] = dependsOn
135139
}
136-
services[name] = service
140+
containers[name] = container
137141
}
138142

139-
dict["services"] = services
143+
dict[key] = containers
140144
}
141145
setNameFromKey(dict)
142146

@@ -154,33 +158,37 @@ func normalizeNetworks(dict map[string]any) {
154158
// implicit `default` network must be introduced only if actually used by some service
155159
usesDefaultNetwork := false
156160

157-
if s, ok := dict["services"]; ok {
158-
services := s.(map[string]any)
159-
for name, se := range services {
160-
service := se.(map[string]any)
161-
if _, ok := service["provider"]; ok {
161+
for _, key := range []string{"services", "jobs"} {
162+
s, ok := dict[key]
163+
if !ok {
164+
continue
165+
}
166+
containers := s.(map[string]any)
167+
for name, se := range containers {
168+
container := se.(map[string]any)
169+
if _, ok := container["provider"]; ok {
162170
continue
163171
}
164-
if _, ok := service["network_mode"]; ok {
172+
if _, ok := container["network_mode"]; ok {
165173
continue
166174
}
167-
if n, ok := service["networks"]; !ok {
168-
// If none explicitly declared, service is connected to default network
169-
service["networks"] = map[string]any{"default": nil}
175+
if n, ok := container["networks"]; !ok {
176+
// If none explicitly declared, container is connected to default network
177+
container["networks"] = map[string]any{"default": nil}
170178
usesDefaultNetwork = true
171179
} else {
172180
net := n.(map[string]any)
173181
if len(net) == 0 {
174182
// networks section declared but empty (corner case)
175-
service["networks"] = map[string]any{"default": nil}
183+
container["networks"] = map[string]any{"default": nil}
176184
usesDefaultNetwork = true
177185
} else if _, ok := net["default"]; ok {
178186
usesDefaultNetwork = true
179187
}
180188
}
181-
services[name] = service
189+
containers[name] = container
182190
}
183-
dict["services"] = services
191+
dict[key] = containers
184192
}
185193

186194
if _, ok := networks["default"]; !ok && usesDefaultNetwork {

loader/omitEmpty.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import "github.com/compose-spec/compose-go/v2/tree"
2020

2121
var omitempty = []tree.Path{
2222
"services.*.dns",
23+
"jobs.*.dns",
2324
}
2425

2526
// OmitEmpty removes empty attributes which are irrelevant when unset

loader/tests/annotations_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,22 @@ services:
3535
image: alpine
3636
annotations:
3737
com.example.foo: bar
38+
jobs:
39+
list:
40+
image: alpine
41+
annotations:
42+
- com.example.foo=bar
43+
map:
44+
image: alpine
45+
annotations:
46+
com.example.foo: bar
3847
`)
3948
expect := func(p *types.Project) {
4049
expected := types.Mapping{"com.example.foo": "bar"}
4150
assert.DeepEqual(t, p.Services["list"].Annotations, expected)
4251
assert.DeepEqual(t, p.Services["map"].Annotations, expected)
52+
assert.DeepEqual(t, p.Jobs["list"].Annotations, expected)
53+
assert.DeepEqual(t, p.Jobs["map"].Annotations, expected)
4354
}
4455
expect(p)
4556

loader/tests/attach_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,24 @@ services:
3535
attach: false
3636
default:
3737
image: alpine
38+
jobs:
39+
attached:
40+
image: alpine
41+
attach: true
42+
detached:
43+
image: alpine
44+
attach: false
45+
default:
46+
image: alpine
3847
`)
3948

4049
expect := func(p *types.Project) {
4150
assert.Equal(t, *p.Services["attached"].Attach, true)
4251
assert.Equal(t, *p.Services["detached"].Attach, false)
4352
assert.Assert(t, p.Services["default"].Attach == nil)
53+
assert.Equal(t, *p.Jobs["attached"].Attach, true)
54+
assert.Equal(t, *p.Jobs["detached"].Attach, false)
55+
assert.Assert(t, p.Jobs["default"].Attach == nil)
4456
}
4557
expect(p)
4658

loader/tests/blkio_config_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,26 @@ services:
4646
device_write_iops:
4747
- path: /dev/sda
4848
rate: 200
49+
jobs:
50+
foo:
51+
image: busybox
52+
blkio_config:
53+
weight: 300
54+
weight_device:
55+
- path: /dev/sda
56+
weight: 400
57+
device_read_bps:
58+
- path: /dev/sda
59+
rate: 1024k
60+
device_write_bps:
61+
- path: /dev/sda
62+
rate: 1024
63+
device_read_iops:
64+
- path: /dev/sda
65+
rate: 100
66+
device_write_iops:
67+
- path: /dev/sda
68+
rate: 200
4969
`)
5070
expect := func(p *types.Project) {
5171
bc := p.Services["foo"].BlkioConfig
@@ -57,6 +77,15 @@ services:
5777
assert.Equal(t, bc.DeviceWriteBps[0].Rate, types.UnitBytes(1024))
5878
assert.Equal(t, bc.DeviceReadIOps[0].Rate, types.UnitBytes(100))
5979
assert.Equal(t, bc.DeviceWriteIOps[0].Rate, types.UnitBytes(200))
80+
jbc := p.Jobs["foo"].BlkioConfig
81+
assert.Equal(t, jbc.Weight, uint16(300))
82+
assert.Equal(t, jbc.WeightDevice[0].Path, "/dev/sda")
83+
assert.Equal(t, jbc.WeightDevice[0].Weight, uint16(400))
84+
assert.Equal(t, jbc.DeviceReadBps[0].Path, "/dev/sda")
85+
assert.Equal(t, jbc.DeviceReadBps[0].Rate, types.UnitBytes(1024*1024))
86+
assert.Equal(t, jbc.DeviceWriteBps[0].Rate, types.UnitBytes(1024))
87+
assert.Equal(t, jbc.DeviceReadIOps[0].Rate, types.UnitBytes(100))
88+
assert.Equal(t, jbc.DeviceWriteIOps[0].Rate, types.UnitBytes(200))
6089
}
6190
expect(p)
6291

0 commit comments

Comments
 (0)