mirror of
				https://github.com/siderolabs/talos.git
				synced 2025-10-31 00:11:36 +01:00 
			
		
		
		
	Fixes #4694 User services run alongside with Talos system services. Every user service container root filesystem should be already present in the Talos root filesystem. Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
		
			
				
	
	
		
			132 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			132 lines
		
	
	
		
			3.4 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 runtime
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 
 | |
| 	"github.com/cosi-project/runtime/pkg/controller"
 | |
| 	"go.uber.org/zap"
 | |
| 	"gopkg.in/yaml.v3"
 | |
| 
 | |
| 	"github.com/talos-systems/talos/internal/app/machined/pkg/system"
 | |
| 	"github.com/talos-systems/talos/internal/app/machined/pkg/system/services"
 | |
| 	extservices "github.com/talos-systems/talos/pkg/machinery/extensions/services"
 | |
| )
 | |
| 
 | |
| // ServiceManager is the interface to the v1alpha1 services subsystems.
 | |
| type ServiceManager interface {
 | |
| 	Load(services ...system.Service) []string
 | |
| 	Start(serviceIDs ...string) error
 | |
| }
 | |
| 
 | |
| // ExtensionServiceController creates extension services based on the extension service configuration found in the rootfs.
 | |
| type ExtensionServiceController struct {
 | |
| 	V1Alpha1Services ServiceManager
 | |
| 	ConfigPath       string
 | |
| }
 | |
| 
 | |
| // Name implements controller.Controller interface.
 | |
| func (ctrl *ExtensionServiceController) Name() string {
 | |
| 	return "runtime.ExtensionServiceController"
 | |
| }
 | |
| 
 | |
| // Inputs implements controller.Controller interface.
 | |
| func (ctrl *ExtensionServiceController) Inputs() []controller.Input {
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Outputs implements controller.Controller interface.
 | |
| func (ctrl *ExtensionServiceController) Outputs() []controller.Output {
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Run implements controller.Controller interface.
 | |
| //
 | |
| //nolint:gocyclo
 | |
| func (ctrl *ExtensionServiceController) Run(ctx context.Context, r controller.Runtime, logger *zap.Logger) error {
 | |
| 	select {
 | |
| 	case <-ctx.Done():
 | |
| 		return nil
 | |
| 	case <-r.EventCh():
 | |
| 	}
 | |
| 
 | |
| 	// controller runs only once, as services are static
 | |
| 	serviceFiles, err := os.ReadDir(ctrl.ConfigPath)
 | |
| 	if err != nil {
 | |
| 		if os.IsNotExist(err) {
 | |
| 			// directory not present, skip completely
 | |
| 			logger.Debug("extension service directory is not found")
 | |
| 
 | |
| 			return nil
 | |
| 		}
 | |
| 
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	extServices := map[string]struct{}{}
 | |
| 
 | |
| 	for _, serviceFile := range serviceFiles {
 | |
| 		if filepath.Ext(serviceFile.Name()) != ".yaml" {
 | |
| 			logger.Debug("skipping config file", zap.String("filename", serviceFile.Name()))
 | |
| 
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		spec, err := ctrl.loadSpec(filepath.Join(ctrl.ConfigPath, serviceFile.Name()))
 | |
| 		if err != nil {
 | |
| 			logger.Error("error loading extension service spec", zap.String("filename", serviceFile.Name()), zap.Error(err))
 | |
| 
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if err = spec.Validate(); err != nil {
 | |
| 			logger.Error("error validating extension service spec", zap.String("filename", serviceFile.Name()), zap.Error(err))
 | |
| 
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if _, exists := extServices[spec.Name]; exists {
 | |
| 			logger.Error("duplicate service spec", zap.String("filename", serviceFile.Name()), zap.String("name", spec.Name))
 | |
| 
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		extServices[spec.Name] = struct{}{}
 | |
| 
 | |
| 		svc := &services.Extension{
 | |
| 			Spec: spec,
 | |
| 		}
 | |
| 
 | |
| 		ctrl.V1Alpha1Services.Load(svc)
 | |
| 
 | |
| 		if err = ctrl.V1Alpha1Services.Start(svc.ID(nil)); err != nil {
 | |
| 			return fmt.Errorf("error starting %q service: %w", spec.Name, err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (ctrl *ExtensionServiceController) loadSpec(path string) (*extservices.Spec, error) {
 | |
| 	var spec extservices.Spec
 | |
| 
 | |
| 	f, err := os.Open(path)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	defer f.Close() //nolint:errcheck
 | |
| 
 | |
| 	if err = yaml.NewDecoder(f).Decode(&spec); err != nil {
 | |
| 		return nil, fmt.Errorf("error unmarshalling extension service config: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	return &spec, nil
 | |
| }
 |