Skip to content
Open
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
28 changes: 26 additions & 2 deletions pkg/build/types/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -199,15 +199,39 @@
"description": "Optional: The shell fragment of the entrypoint command"
},
"services": {
"additionalProperties": {
"$ref": "#/$defs/ImageServices"
}
},
"additionalProperties": false,
"type": "object"
},
"ImageService": {
"properties": {
"command": {
"type": "string",
"description": "Required: The command of the service"
},
"depends_on": {
"items": {
"type": "string"
},
"type": "object"
"type": "array",
"description": "Optional: The name of the parent service"
},
"restart": {
"type": "string",
"description": "Optional: The restart policy of the service ('always' by default as in docker)"
}
},
"additionalProperties": false,
"type": "object"
},
"ImageServices": {
"additionalProperties": {
"$ref": "#/$defs/ImageService"
},
"type": "object"
},
"Layering": {
"properties": {
"strategy": {
Expand Down
40 changes: 39 additions & 1 deletion pkg/build/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"sort"

v1 "github.com/google/go-containerregistry/pkg/v1"
"gopkg.in/yaml.v3"
)

type User struct {
Expand Down Expand Up @@ -130,6 +131,17 @@ func (i ImageContents) MarshalYAML() (interface{}, error) {
return ri, nil
}

type ImageService struct {
// Required: The command of the service
Command string `json:"command,omitempty" yaml:"command"`
// Optional: The name of the parent service
DependsOn []string `json:"depends_on,omitempty" yaml:"depends_on"`
// Optional: The restart policy of the service ('always' by default as in docker)
Restart string `json:"restart,omitempty" yaml:"restart"`
}

type ImageServices map[string]ImageService

type ImageEntrypoint struct {
// Optional: The type of entrypoint. Only "service-bundle" is supported.
Type string `json:"type,omitempty"`
Expand All @@ -138,7 +150,33 @@ type ImageEntrypoint struct {
// Optional: The shell fragment of the entrypoint command
ShellFragment string `json:"shell-fragment,omitempty" yaml:"shell-fragment"`

Services map[string]string `json:"services,omitempty"`
Services ImageServices `json:"services,omitempty"`
}

func (s *ImageServices) UnmarshalYAML(value *yaml.Node) error {
// First attempt to parse as map[string]ImageService
var structured map[string]ImageService
if err := value.Decode(&structured); err == nil {
*s = structured
return nil
}

// If err, attempt as map[string]string
var flat map[string]string
if err := value.Decode(&flat); err == nil {
converted := make(map[string]ImageService)
for name, cmd := range flat {
converted[name] = ImageService{
Command: cmd,
DependsOn: []string{},
Restart: "always",
}
}
*s = converted
return nil
}

return fmt.Errorf("services field must be map[string]string or map[string]ImageService")
}

type ImageAccounts struct {
Expand Down
2 changes: 0 additions & 2 deletions pkg/s6/s6.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ import (
apkfs "chainguard.dev/apko/pkg/apk/fs"
)

type Services map[string]string

type Context struct {
fs apkfs.FullFS
}
Expand Down
36 changes: 34 additions & 2 deletions pkg/s6/supervision_tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ import (
"context"
"fmt"
"path/filepath"
"strings"

apkbuildtypes "chainguard.dev/apko/pkg/build/types"
"github.com/chainguard-dev/clog"
)

func (sc *Context) WriteSupervisionTree(ctx context.Context, services Services) error {
func (sc *Context) WriteSupervisionTree(ctx context.Context, services apkbuildtypes.ImageServices) error {
log := clog.FromContext(ctx)
log.Debug("generating supervision tree")

Expand All @@ -33,9 +35,39 @@ func (sc *Context) WriteSupervisionTree(ctx context.Context, services Services)
return fmt.Errorf("could not make supervision directory: %w", err)
}

if err := sc.fs.WriteFile(filepath.Join(svcdir, "run"), []byte(fmt.Sprintf("#!/bin/execlineb\n%s\n", svccmd)), 0755); err != nil {
// Construct dependencies by adding 's6-svwait' if needed, then execute the main command
runContent := "#!/bin/execlineb\n"
for _, dep := range svccmd.DependsOn {
runContent += fmt.Sprintf("foreground { s6-svwait -D /sv/%s }\n", dep)
}
runContent += svccmd.Command

if err := sc.fs.WriteFile(filepath.Join(svcdir, "run"), []byte(runContent), 0755); err != nil {
return fmt.Errorf("could not write runfile: %w", err)
}

// Manage finish scripts according to defined restart policy
restartPolicy := strings.ToLower(strings.TrimSpace(svccmd.Restart))

var finishContent string
switch restartPolicy {
case "no":
// Always send down signal
finishContent = fmt.Sprintf(`#!/bin/execlineb
s6-svc -D /sv/%s`, service)
case "on-failure":
// Send down signal in case the run script exited with zero value
finishContent = fmt.Sprintf(`#!/bin/execlineb -s1
if { eltest ${1} -eq 0 }
s6-svc -D /sv/%s
`, service)
}

if finishContent != "" {
if err := sc.fs.WriteFile(filepath.Join(svcdir, "finish"), []byte(finishContent), 0755); err != nil {
return fmt.Errorf("could not write finishfile: %w", err)
}
}
}

return nil
Expand Down