mirror of
https://github.com/siderolabs/talos.git
synced 2025-09-15 10:51:12 +02:00
Containerd doesn't support merging plugin configuration from multiple sources, and Talos has several pieces which configure CRI plugin: (see https://github.com/containerd/containerd/issues/5837) * base config * registry mirror config * system extensions * ... So we implement our own simple way of merging config parts (by simply concatenating text files) to build a final `cri.toml`. At the same time containerd migrated to a new format to specify registry mirror configuration, while old way (via CRI config) is going to be removed in 1.7.0. New way also allows to apply most of registry configuration (except for auth) on the fly. Also, containerd was updated to 1.6.0-rc.0 and runc to 1.1.0. Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
191 lines
5.5 KiB
Go
191 lines
5.5 KiB
Go
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
package files
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/cosi-project/runtime/pkg/controller"
|
|
"github.com/cosi-project/runtime/pkg/resource"
|
|
"go.uber.org/zap"
|
|
"golang.org/x/sys/unix"
|
|
|
|
"github.com/talos-systems/talos/pkg/machinery/resources/files"
|
|
)
|
|
|
|
// EtcFileController watches EtcFileSpecs, creates/updates files.
|
|
type EtcFileController struct {
|
|
// Path to /etc directory, read-only filesystem.
|
|
EtcPath string
|
|
// Shadow path where actual file will be created and bind mounted into EtcdPath.
|
|
ShadowPath string
|
|
|
|
// Cache of bind mounts created.
|
|
bindMounts map[string]interface{}
|
|
}
|
|
|
|
// Name implements controller.Controller interface.
|
|
func (ctrl *EtcFileController) Name() string {
|
|
return "files.EtcFileController"
|
|
}
|
|
|
|
// Inputs implements controller.Controller interface.
|
|
func (ctrl *EtcFileController) Inputs() []controller.Input {
|
|
return []controller.Input{
|
|
{
|
|
Namespace: files.NamespaceName,
|
|
Type: files.EtcFileSpecType,
|
|
Kind: controller.InputStrong,
|
|
},
|
|
}
|
|
}
|
|
|
|
// Outputs implements controller.Controller interface.
|
|
func (ctrl *EtcFileController) Outputs() []controller.Output {
|
|
return []controller.Output{
|
|
{
|
|
Type: files.EtcFileStatusType,
|
|
Kind: controller.OutputExclusive,
|
|
},
|
|
}
|
|
}
|
|
|
|
// Run implements controller.Controller interface.
|
|
//
|
|
//nolint:gocyclo,cyclop
|
|
func (ctrl *EtcFileController) Run(ctx context.Context, r controller.Runtime, logger *zap.Logger) error {
|
|
if ctrl.bindMounts == nil {
|
|
ctrl.bindMounts = make(map[string]interface{})
|
|
}
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil
|
|
case <-r.EventCh():
|
|
}
|
|
|
|
list, err := r.List(ctx, resource.NewMetadata(files.NamespaceName, files.EtcFileSpecType, "", resource.VersionUndefined))
|
|
if err != nil {
|
|
return fmt.Errorf("error listing specs: %w", err)
|
|
}
|
|
|
|
// add finalizers for all live resources
|
|
for _, res := range list.Items {
|
|
if res.Metadata().Phase() != resource.PhaseRunning {
|
|
continue
|
|
}
|
|
|
|
if err = r.AddFinalizer(ctx, res.Metadata(), ctrl.Name()); err != nil {
|
|
return fmt.Errorf("error adding finalizer: %w", err)
|
|
}
|
|
}
|
|
|
|
touchedIDs := make(map[resource.ID]struct{})
|
|
|
|
for _, item := range list.Items {
|
|
spec := item.(*files.EtcFileSpec) //nolint:errcheck,forcetypeassert
|
|
filename := spec.Metadata().ID()
|
|
_, mountExists := ctrl.bindMounts[filename]
|
|
|
|
src := filepath.Join(ctrl.ShadowPath, filename)
|
|
dst := filepath.Join(ctrl.EtcPath, filename)
|
|
|
|
switch spec.Metadata().Phase() {
|
|
case resource.PhaseTearingDown:
|
|
if mountExists {
|
|
logger.Debug("removing bind mount", zap.String("src", src), zap.String("dst", dst))
|
|
|
|
if err = unix.Unmount(dst, 0); err != nil && !errors.Is(err, os.ErrNotExist) {
|
|
return fmt.Errorf("failed to unmount bind mount %q: %w", dst, err)
|
|
}
|
|
|
|
delete(ctrl.bindMounts, filename)
|
|
}
|
|
|
|
logger.Debug("removing file", zap.String("src", src))
|
|
|
|
if err = os.Remove(src); err != nil && !errors.Is(err, os.ErrNotExist) {
|
|
return fmt.Errorf("failed to remove %q: %w", src, err)
|
|
}
|
|
|
|
// now remove finalizer as the link was deleted
|
|
if err = r.RemoveFinalizer(ctx, spec.Metadata(), ctrl.Name()); err != nil {
|
|
return fmt.Errorf("error removing finalizer: %w", err)
|
|
}
|
|
case resource.PhaseRunning:
|
|
if !mountExists {
|
|
logger.Debug("creating bind mount", zap.String("src", src), zap.String("dst", dst))
|
|
|
|
if err = createBindMount(src, dst, spec.TypedSpec().Mode); err != nil {
|
|
return fmt.Errorf("failed to create shadow bind mount %q -> %q: %w", src, dst, err)
|
|
}
|
|
|
|
ctrl.bindMounts[filename] = struct{}{}
|
|
}
|
|
|
|
logger.Debug("writing file contents", zap.String("dst", dst), zap.Stringer("version", spec.Metadata().Version()))
|
|
|
|
if err = os.WriteFile(dst, spec.TypedSpec().Contents, spec.TypedSpec().Mode); err != nil {
|
|
return fmt.Errorf("error updating %q: %w", dst, err)
|
|
}
|
|
|
|
if err = r.Modify(ctx, files.NewEtcFileStatus(files.NamespaceName, filename), func(r resource.Resource) error {
|
|
r.(*files.EtcFileStatus).TypedSpec().SpecVersion = spec.Metadata().Version().String()
|
|
|
|
return nil
|
|
}); err != nil {
|
|
return fmt.Errorf("error updating status: %w", err)
|
|
}
|
|
|
|
touchedIDs[filename] = struct{}{}
|
|
}
|
|
}
|
|
|
|
// list statuses for cleanup
|
|
list, err = r.List(ctx, resource.NewMetadata(files.NamespaceName, files.EtcFileStatusType, "", resource.VersionUndefined))
|
|
if err != nil {
|
|
return fmt.Errorf("error listing resources: %w", err)
|
|
}
|
|
|
|
for _, res := range list.Items {
|
|
if _, ok := touchedIDs[res.Metadata().ID()]; !ok {
|
|
if err = r.Destroy(ctx, res.Metadata()); err != nil {
|
|
return fmt.Errorf("error cleaning up specs: %w", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// createBindMount creates a common way to create a writable source file with a
|
|
// bind mounted destination. This is most commonly used for well known files
|
|
// under /etc that need to be adjusted during startup.
|
|
func createBindMount(src, dst string, mode os.FileMode) (err error) {
|
|
if err = os.MkdirAll(filepath.Dir(src), 0o755); err != nil {
|
|
return err
|
|
}
|
|
|
|
var f *os.File
|
|
|
|
if f, err = os.OpenFile(src, os.O_WRONLY|os.O_CREATE, mode); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = f.Close(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = unix.Mount(src, dst, "", unix.MS_BIND|unix.MS_RDONLY, ""); err != nil {
|
|
return fmt.Errorf("failed to create bind mount for %s: %w", dst, err)
|
|
}
|
|
|
|
return nil
|
|
}
|