Skip to content
Merged

Jobs #866

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
# required for golangci-lint on Windows
*.go text eol=lf
# golden files are compared byte-for-byte against marshaled output
loader/testdata/golden/* text eol=lf
8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ test: ## Run tests
fmt: ## Format go files
go fmt ./...

.PHONY: generate
generate: ## Regenerate derived sources (container-spec attribute list)
go run ./internal/generate/containerspec

.PHONY: check-generate
check-generate: generate ## Fail if derived sources are out of sync
git diff --exit-code types/container_spec_attributes.gen.go

.PHONY: deepcopy
deepcopy: build-validate-image
docker run --rm -v .:/go/src $(IMAGE_PREFIX)validate goderive ./types/...
Expand Down
62 changes: 31 additions & 31 deletions graph/graph_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ import (

func TestTraversalWithMultipleParents(t *testing.T) {
dependent := types.ServiceConfig{
Name: "dependent",
DependsOn: make(types.DependsOnConfig),
Name: "dependent",
WorkloadSpec: types.WorkloadSpec{DependsOn: make(types.DependsOnConfig)},
}

project := types.Project{
Expand Down Expand Up @@ -118,8 +118,8 @@ func TestBuildGraph(t *testing.T) {
desc: "builds graph with single service",
services: types.Services{
"test": {
Name: "test",
DependsOn: types.DependsOnConfig{},
Name: "test",
WorkloadSpec: types.WorkloadSpec{DependsOn: types.DependsOnConfig{}},
},
},
expectedVertices: map[string]*vertex[types.ServiceConfig]{
Expand All @@ -135,12 +135,12 @@ func TestBuildGraph(t *testing.T) {
desc: "builds graph with two separate services",
services: types.Services{
"test": {
Name: "test",
DependsOn: types.DependsOnConfig{},
Name: "test",
WorkloadSpec: types.WorkloadSpec{DependsOn: types.DependsOnConfig{}},
},
"another": {
Name: "another",
DependsOn: types.DependsOnConfig{},
Name: "another",
WorkloadSpec: types.WorkloadSpec{DependsOn: types.DependsOnConfig{}},
},
},
expectedVertices: map[string]*vertex[types.ServiceConfig]{
Expand All @@ -163,13 +163,13 @@ func TestBuildGraph(t *testing.T) {
services: types.Services{
"test": {
Name: "test",
DependsOn: types.DependsOnConfig{
WorkloadSpec: types.WorkloadSpec{DependsOn: types.DependsOnConfig{
"another": types.ServiceDependency{},
},
}},
},
"another": {
Name: "another",
DependsOn: types.DependsOnConfig{},
Name: "another",
WorkloadSpec: types.WorkloadSpec{DependsOn: types.DependsOnConfig{}},
},
},
expectedVertices: map[string]*vertex[types.ServiceConfig]{
Expand All @@ -196,11 +196,11 @@ func TestBuildGraph(t *testing.T) {
services: types.Services{
"test": {
Name: "test",
DependsOn: types.DependsOnConfig{
WorkloadSpec: types.WorkloadSpec{DependsOn: types.DependsOnConfig{
"another": types.ServiceDependency{
Required: false,
},
},
}},
},
},
expectedVertices: map[string]*vertex[types.ServiceConfig]{
Expand All @@ -217,11 +217,11 @@ func TestBuildGraph(t *testing.T) {
services: types.Services{
"test": {
Name: "test",
DependsOn: types.DependsOnConfig{
WorkloadSpec: types.WorkloadSpec{DependsOn: types.DependsOnConfig{
"another": types.ServiceDependency{
Required: true,
},
},
}},
},
},
expectedError: `service "test" depends on unknown service "another"`,
Expand All @@ -231,18 +231,18 @@ func TestBuildGraph(t *testing.T) {
services: types.Services{
"test": {
Name: "test",
DependsOn: types.DependsOnConfig{
WorkloadSpec: types.WorkloadSpec{DependsOn: types.DependsOnConfig{
"another": types.ServiceDependency{
Required: true,
},
},
}},
},
},
disabled: types.Services{
"another": {
Name: "another",
Profiles: []string{"test"},
DependsOn: types.DependsOnConfig{},
Name: "another",
Profiles: []string{"test"},
WorkloadSpec: types.WorkloadSpec{DependsOn: types.DependsOnConfig{}},
},
},
expectedError: `service "another" is required by "test" but is disabled. Can be enabled by profiles [test]`,
Expand All @@ -252,19 +252,19 @@ func TestBuildGraph(t *testing.T) {
services: types.Services{
"test": {
Name: "test",
DependsOn: types.DependsOnConfig{
WorkloadSpec: types.WorkloadSpec{DependsOn: types.DependsOnConfig{
"another": types.ServiceDependency{},
},
}},
},
"another": {
Name: "another",
DependsOn: types.DependsOnConfig{
WorkloadSpec: types.WorkloadSpec{DependsOn: types.DependsOnConfig{
"another_dep": types.ServiceDependency{},
},
}},
},
"another_dep": {
Name: "another_dep",
DependsOn: types.DependsOnConfig{},
Name: "another_dep",
WorkloadSpec: types.WorkloadSpec{DependsOn: types.DependsOnConfig{}},
},
},
expectedVertices: map[string]*vertex[types.ServiceConfig]{
Expand Down Expand Up @@ -434,15 +434,15 @@ func exampleProject() *types.Project {
Services: types.Services{
"test1": {
Name: "test1",
DependsOn: map[string]types.ServiceDependency{
WorkloadSpec: types.WorkloadSpec{DependsOn: map[string]types.ServiceDependency{
"test2": {},
},
}},
},
"test2": {
Name: "test2",
DependsOn: map[string]types.ServiceDependency{
WorkloadSpec: types.WorkloadSpec{DependsOn: map[string]types.ServiceDependency{
"test3": {},
},
}},
},
"test3": {
Name: "test3",
Expand Down
62 changes: 62 additions & 0 deletions internal/generate/containerspec/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
Copyright 2020 The Compose Specification Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Command containerspec generates types/container_spec_attributes.gen.go,
// the compile-time list of the container specification's yaml attributes.
// Reflection happens here, at generation time, so consumers get a plain
// source-level list that cannot drift from the ContainerSpec type as long
// as generation is part of the build validation.
package main

import (
"fmt"
"os"
"reflect"
"sort"
"strings"

"github.com/compose-spec/compose-go/v2/types"
)

func main() {
var keys []string
t := reflect.TypeOf(types.ContainerSpec{})
for i := range t.NumField() {
tag, _, _ := strings.Cut(t.Field(i).Tag.Get("yaml"), ",")
if tag != "" && tag != "-" && !strings.HasPrefix(tag, "#") {
keys = append(keys, tag)
}
}
sort.Strings(keys)

var b strings.Builder
b.WriteString("// Code generated by internal/generate/containerspec. DO NOT EDIT.\n\n")
b.WriteString("package types\n\n")
b.WriteString("// ContainerSpecAttributes lists the yaml attributes of ContainerSpec —\n")
b.WriteString("// the boundary between the container specification and workload or\n")
b.WriteString("// service-only attributes, as consumed by the pre_start inheritance\n")
b.WriteString("// resolution.\n")
b.WriteString("var ContainerSpecAttributes = []string{\n")
for _, k := range keys {
fmt.Fprintf(&b, "\t%q,\n", k)
}
b.WriteString("}\n")

if err := os.WriteFile("types/container_spec_attributes.gen.go", []byte(b.String()), 0o644); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
21 changes: 13 additions & 8 deletions loader/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,27 +25,32 @@ import (
// ResolveEnvironment update the environment variables for the format {- VAR} (without interpolation)
func ResolveEnvironment(dict map[string]any, environment types.Mapping) {
resolveServicesEnvironment(dict, environment)
resolveContainerEnvironment(dict, "jobs", environment)
resolveSecretsEnvironment(dict, environment)
resolveConfigsEnvironment(dict, environment)
}

func resolveServicesEnvironment(dict map[string]any, environment types.Mapping) {
services, ok := dict["services"].(map[string]any)
resolveContainerEnvironment(dict, "services", environment)
}

func resolveContainerEnvironment(dict map[string]any, key string, environment types.Mapping) {
containers, ok := dict[key].(map[string]any)
if !ok {
return
}

for service, cfg := range services {
serviceConfig, ok := cfg.(map[string]any)
for name, cfg := range containers {
config, ok := cfg.(map[string]any)
if !ok {
continue
}
serviceEnv, ok := serviceConfig["environment"].([]any)
envList, ok := config["environment"].([]any)
if !ok {
continue
}
envs := []any{}
for _, env := range serviceEnv {
for _, env := range envList {
varEnv, ok := env.(string)
if !ok {
continue
Expand All @@ -57,10 +62,10 @@ func resolveServicesEnvironment(dict map[string]any, environment types.Mapping)
envs = append(envs, varEnv)
}
}
serviceConfig["environment"] = envs
services[service] = serviceConfig
config["environment"] = envs
containers[name] = config
}
dict["services"] = services
dict[key] = containers
}

func resolveSecretsEnvironment(dict map[string]any, environment types.Mapping) {
Expand Down
30 changes: 16 additions & 14 deletions loader/extends.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,24 @@ import (
)

func ApplyExtends(ctx context.Context, dict map[string]any, opts *Options, tracker *cycleTracker, post PostProcessor) error {
a, ok := dict["services"]
if !ok {
return nil
}
services, ok := a.(map[string]any)
if !ok {
return fmt.Errorf("services must be a mapping")
}
for name := range services {
merged, err := applyServiceExtends(ctx, name, services, opts, tracker, post)
if err != nil {
return err
for _, key := range []string{"services", "jobs"} {
a, ok := dict[key]
if !ok {
continue
}
entries, ok := a.(map[string]any)
if !ok {
return fmt.Errorf("%s must be a mapping", key)
}
for name := range entries {
merged, err := applyServiceExtends(ctx, name, entries, opts, tracker, post)
if err != nil {
return err
}
entries[name] = merged
}
services[name] = merged
dict[key] = entries
}
dict["services"] = services
return nil
}

Expand Down
Loading