talos
165 строк · 4.6 Кб
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5// Package services contains definitions for non-system services.
6package services
7
8import (
9"errors"
10"fmt"
11"path/filepath"
12"regexp"
13
14"github.com/hashicorp/go-multierror"
15"github.com/opencontainers/runtime-spec/specs-go"
16
17"github.com/siderolabs/talos/pkg/machinery/nethelpers"
18)
19
20// Spec is represents non-system service definition.
21type Spec struct {
22// Name of the service to run, will be prefixed with `ext-` when registered as Talos service.
23//
24// Valid: [-_a-z0-9]+
25Name string `yaml:"name"`
26// Container to run.
27//
28// Container rootfs should be extracted to the /usr/local/lib/containers/<name>.
29Container Container `yaml:"container"`
30// Service dependencies.
31Depends []Dependency `yaml:"depends"`
32// Restart configuration.
33Restart RestartKind `yaml:"restart"`
34}
35
36// Container specifies service container to run.
37type Container struct {
38// Entrypoint for the service, relative to the container rootfs.
39Entrypoint string `yaml:"entrypoint"`
40// Environment variables for the service.
41Environment []string `yaml:"environment"`
42// EnvironmentFile to load environment vars before running the service.
43EnvironmentFile string `yaml:"environmentFile"`
44// Args to pass to the entrypoint.
45Args []string `yaml:"args"`
46// Volume mounts.
47Mounts []specs.Mount `yaml:"mounts"`
48// Security options.
49Security Security `yaml:"security"`
50}
51
52// Security options for containers.
53type Security struct {
54// WriteableSysfs makes the '/sys' path writeable in the container namespace if set to true.
55WriteableSysfs bool `yaml:"writeableSysfs"`
56// MaskedPaths is a list of paths in the container namespace that should not be readable.
57MaskedPaths []string `yaml:"maskedPaths"`
58// ReadonlyPaths is a list of paths in the container namespace that should be read-only.
59ReadonlyPaths []string `yaml:"readonlyPaths"`
60// WriteableRootfs
61WriteableRootfs bool `yaml:"writeableRootfs"`
62// RootfsPropagation is the propagation mode for the rootfs mount.
63RootfsPropagation string `yaml:"rootfsPropagation,omitempty"`
64}
65
66// Dependency describes a service Dependency.
67//
68// Only a single dependency out of the list might be specified.
69type Dependency struct {
70// Depends on a service being running and healthy (if health checks are available).
71Service string `yaml:"service,omitempty"`
72// Depends on file/directory existence.
73Path string `yaml:"path,omitempty"`
74// Network readiness checks.
75//
76// Valid options are nethelpers.Status string values.
77Network []nethelpers.Status `yaml:"network,omitempty"`
78// Time sync check.
79Time bool `yaml:"time,omitempty"`
80// Depends on configuration files to be present.
81Configuration bool `yaml:"configuration,omitempty"`
82}
83
84var nameRe = regexp.MustCompile(`^[-_a-z0-9]{1,}$`)
85
86// Validate the service spec.
87func (spec *Spec) Validate() error {
88var multiErr *multierror.Error
89
90if !nameRe.MatchString(spec.Name) {
91multiErr = multierror.Append(multiErr, fmt.Errorf("name %q is invalid", spec.Name))
92}
93
94if !spec.Restart.IsARestartKind() {
95multiErr = multierror.Append(multiErr, fmt.Errorf("restart kind is invalid: %s", spec.Restart))
96}
97
98multiErr = multierror.Append(multiErr, spec.Container.Validate())
99
100for _, dep := range spec.Depends {
101multiErr = multierror.Append(multiErr, dep.Validate())
102}
103
104return multiErr.ErrorOrNil()
105}
106
107// Validate the container spec.
108func (ctr *Container) Validate() error {
109var multiErr *multierror.Error
110
111if ctr.Entrypoint == "" {
112multiErr = multierror.Append(multiErr, errors.New("container endpoint can't be empty"))
113}
114
115return multiErr.ErrorOrNil()
116}
117
118// Validate the dependency spec.
119//
120//nolint:gocyclo
121func (dep *Dependency) Validate() error {
122var multiErr *multierror.Error
123
124nonZeroDeps := 0
125
126if dep.Service != "" {
127nonZeroDeps++
128}
129
130if dep.Path != "" {
131nonZeroDeps++
132
133if !filepath.IsAbs(dep.Path) {
134multiErr = multierror.Append(multiErr, fmt.Errorf("path is not absolute: %q", dep.Path))
135}
136}
137
138if len(dep.Network) > 0 {
139nonZeroDeps++
140
141for _, st := range dep.Network {
142if !st.IsAStatus() {
143multiErr = multierror.Append(multiErr, fmt.Errorf("invalid network dependency: %s", st))
144}
145}
146}
147
148if dep.Time {
149nonZeroDeps++
150}
151
152if dep.Configuration {
153nonZeroDeps++
154}
155
156if nonZeroDeps == 0 {
157multiErr = multierror.Append(multiErr, errors.New("no dependency specified"))
158}
159
160if nonZeroDeps > 1 {
161multiErr = multierror.Append(multiErr, errors.New("more than a single dependency is set"))
162}
163
164return multiErr.ErrorOrNil()
165}
166