blob: 9363abd5d084e34e2590a84e9a7df77324f39a10 [file] [edit]
package daemon
import (
"context"
"fmt"
"os"
"path/filepath"
"github.com/containerd/log"
"github.com/moby/extensions"
"github.com/moby/extensions/grpcproxy"
"github.com/moby/extensions/host"
"github.com/moby/moby/v2/daemon/config"
"github.com/moby/moby/v2/daemon/internal/rootless"
servicegrpcv0 "github.com/moby/moby/v2/extpoints/servicegrpc/v0"
"github.com/moby/moby/v2/pkg/homedir"
"google.golang.org/grpc"
)
// newExtensionHost builds the daemon's extension host.
func (daemon *Daemon) newExtensionHost(ctx context.Context) (*host.Host, error) {
cfg := daemon.Config()
runtimeDir := filepath.Join(cfg.ExecRoot, "extensions")
if err := os.MkdirAll(runtimeDir, 0o700); err != nil {
return nil, fmt.Errorf("create extension runtime directory: %w", err)
}
if err := os.Chmod(runtimeDir, 0o700); err != nil {
return nil, fmt.Errorf("set permissions on extension runtime directory: %w", err)
}
return host.New(ctx,
host.WithRuntimeDir(runtimeDir),
host.WithExtensions(builtinExtensions...),
host.WithExtensions(daemon.daemonExtensions()...),
host.WithDirs(extensionDirs(&cfg)...),
host.WithClientProviders(clientProviders()...),
host.WithProviderPolicy(host.PointPolicyFunc(func(extensions.ExtensionIdentity, extensions.PointID) host.PointPolicyResult {
return host.Allow()
})),
host.WithDependencyProviders(dependencyProviders()...),
host.WithExtensionConfig(extensionConfig(&cfg)),
)
}
// extensionConfig converts daemon.json's extension-config entries to host form.
func extensionConfig(cfg *config.Config) map[extensions.ExtensionID]extensions.Config {
if len(cfg.ExtensionConfig) == 0 {
return nil
}
out := make(map[extensions.ExtensionID]extensions.Config, len(cfg.ExtensionConfig))
for _, extensionConfig := range cfg.ExtensionConfig {
out[extensionConfig.ID] = extensionConfig.Config
}
return out
}
// extensionDirs are the directories scanned for out-of-process extension
// binaries: the ones configured with --extension-dir, or the default location
// when none are configured.
func extensionDirs(cfg *config.Config) []string {
if len(cfg.ExtensionDirs) > 0 {
return cfg.ExtensionDirs
}
dir, err := defaultExtensionDir()
if err != nil {
// Only rootless without a home directory reaches here; without a default
// the daemon simply loads no extensions rather than failing to start.
log.G(context.TODO()).WithError(err).Debug("extensions: no default directory")
return nil
}
return []string{dir}
}
// defaultExtensionDir is the standard location for extension binaries:
// /usr/libexec/docker/moby-extensions, or the rootless equivalent under the
// user's libexec home.
func defaultExtensionDir() (string, error) {
libexecDir := "/usr/libexec"
if rootless.RunningWithRootlessKit() {
var err error
libexecDir, err = homedir.GetLibexecHome()
if err != nil {
return "", err
}
}
return filepath.Join(libexecDir, "docker", "moby-extensions"), nil
}
// ExposeExtensionServices publishes services selected through service.grpc on
// the API socket. In-process services are registered on gs; out-of-process
// services are proxied by name. Service-name collisions fail startup.
func (daemon *Daemon) ExposeExtensionServices(gs *grpc.Server) (*grpcproxy.Proxy, error) {
if daemon.extensionHost == nil {
return nil, nil
}
reserved := make(map[string]struct{})
for name := range gs.GetServiceInfo() {
reserved[name] = struct{}{}
}
inproc, err := servicegrpcv0.Collect(daemon.extensionHost)
if err != nil {
return nil, err
}
for _, svc := range inproc {
if _, taken := reserved[svc.Name]; taken {
return nil, fmt.Errorf("in-process extension cannot expose gRPC service %q: it is already served", svc.Name)
}
reserved[svc.Name] = struct{}{}
}
for _, svc := range inproc {
gs.RegisterService(svc.Desc, svc.Impl)
}
var backends []grpcproxy.Backend
for ext, names := range daemon.extensionHost.PublishedServicesForPoint(servicegrpcv0.Point.ID()) {
if conn, ok := daemon.extensionHost.Conn(ext); ok {
backends = append(backends, grpcproxy.Backend{ID: string(ext), Conn: conn, Services: names})
}
}
routes, err := grpcproxy.BuildRoutes(backends, reserved)
if err != nil {
return nil, err
}
if len(routes) == 0 {
return nil, nil
}
return grpcproxy.New(routes), nil
}