diff --git a/cmd/finch-daemon/main.go b/cmd/finch-daemon/main.go index afdc12dc..a9a69321 100644 --- a/cmd/finch-daemon/main.go +++ b/cmd/finch-daemon/main.go @@ -21,33 +21,19 @@ import ( // register HTTP handler for /debug/pprof on the DefaultServeMux. _ "net/http/pprof" - "github.com/containerd/containerd" - "github.com/containerd/nerdctl/pkg/api/types" - "github.com/containerd/nerdctl/pkg/config" "github.com/coreos/go-systemd/v22/daemon" - "github.com/sirupsen/logrus" - "github.com/spf13/afero" - "github.com/spf13/cobra" - "github.com/runfinch/finch-daemon/api/router" - "github.com/runfinch/finch-daemon/internal/backend" - "github.com/runfinch/finch-daemon/internal/service/builder" - "github.com/runfinch/finch-daemon/internal/service/container" - "github.com/runfinch/finch-daemon/internal/service/exec" - "github.com/runfinch/finch-daemon/internal/service/image" - "github.com/runfinch/finch-daemon/internal/service/network" - "github.com/runfinch/finch-daemon/internal/service/system" - "github.com/runfinch/finch-daemon/internal/service/volume" - "github.com/runfinch/finch-daemon/pkg/archive" - "github.com/runfinch/finch-daemon/pkg/ecc" "github.com/runfinch/finch-daemon/pkg/flog" "github.com/runfinch/finch-daemon/version" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" ) const ( // Keep this value in sync with `guestSocket` in README.md. - defaultFinchAddr = "/run/finch.sock" - defaultNamespace = "finch" + defaultFinchAddr = "/run/finch.sock" + defaultNamespace = "finch" + defaultConfigPath = "/etc/finch/finch.toml" ) type DaemonOptions struct { @@ -55,6 +41,7 @@ type DaemonOptions struct { socketAddr string socketOwner int debugAddress string + configPath string } var options = new(DaemonOptions) @@ -71,6 +58,7 @@ func main() { rootCmd.Flags().BoolVar(&options.debug, "debug", false, "turn on debug log level") rootCmd.Flags().IntVar(&options.socketOwner, "socket-owner", -1, "Uid and Gid of the server socket") rootCmd.Flags().StringVar(&options.debugAddress, "debug-addr", "", "") + rootCmd.Flags().StringVar(&options.configPath, "config-file", defaultConfigPath, "Daemon Config Path") if err := rootCmd.Execute(); err != nil { log.Printf("got error: %v", err) log.Fatal(err) @@ -88,7 +76,7 @@ func run(options *DaemonOptions) error { } logger := flog.NewLogrus() - r, err := newRouter(options.debug, logger) + r, err := newRouter(options, logger) if err != nil { return fmt.Errorf("failed to create a router: %w", err) } @@ -145,37 +133,23 @@ func run(options *DaemonOptions) error { return nil } -func newRouter(debug bool, logger *flog.Logrus) (http.Handler, error) { - conf := config.New() - conf.Debug = debug - conf.Namespace = defaultNamespace - client, err := containerd.New(conf.Address, containerd.WithDefaultNamespace(conf.Namespace)) +func newRouter(options *DaemonOptions, logger *flog.Logrus) (http.Handler, error) { + conf, err := initializeConfig(options) if err != nil { - return nil, fmt.Errorf("failed to create containerd client: %w", err) + return nil, err } - clientWrapper := backend.NewContainerdClientWrapper(client) - // GlobalCommandOptions is actually just an alias for Config, see - // https://github.com/containerd/nerdctl/blob/9f8655f7722d6e6851755123730436bf1a6c9995/pkg/api/types/global.go#L21 - globalOptions := (*types.GlobalCommandOptions)(conf) - ncWrapper := backend.NewNerdctlWrapper(clientWrapper, globalOptions) - if _, err = ncWrapper.GetNerdctlExe(); err != nil { - return nil, fmt.Errorf("failed to find nerdctl binary: %w", err) + + clientWrapper, err := createContainerdClient(conf) + if err != nil { + return nil, err } - fs := afero.NewOsFs() - execCmdCreator := ecc.NewExecCmdCreator() - tarCreator := archive.NewTarCreator(execCmdCreator, logger) - tarExtractor := archive.NewTarExtractor(execCmdCreator, logger) - opts := &router.Options{ - Config: conf, - ContainerService: container.NewService(clientWrapper, ncWrapper, logger, fs, tarCreator, tarExtractor), - ImageService: image.NewService(clientWrapper, ncWrapper, logger), - NetworkService: network.NewService(clientWrapper, ncWrapper, logger), - SystemService: system.NewService(clientWrapper, ncWrapper, logger), - BuilderService: builder.NewService(clientWrapper, ncWrapper, logger, tarExtractor), - VolumeService: volume.NewService(ncWrapper, logger), - ExecService: exec.NewService(clientWrapper, logger), - NerdctlWrapper: ncWrapper, + + ncWrapper, err := createNerdctlWrapper(clientWrapper, conf) + if err != nil { + return nil, err } + + opts := createRouterOptions(conf, clientWrapper, ncWrapper, logger) return router.New(opts), nil } diff --git a/cmd/finch-daemon/router_utils.go b/cmd/finch-daemon/router_utils.go new file mode 100644 index 00000000..ceec5741 --- /dev/null +++ b/cmd/finch-daemon/router_utils.go @@ -0,0 +1,114 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "errors" + "fmt" + "os" + + "github.com/containerd/containerd" + "github.com/containerd/containerd/namespaces" + "github.com/containerd/nerdctl/pkg/api/types" + "github.com/containerd/nerdctl/pkg/config" + toml "github.com/pelletier/go-toml/v2" + "github.com/runfinch/finch-daemon/api/router" + "github.com/runfinch/finch-daemon/internal/backend" + "github.com/runfinch/finch-daemon/internal/service/builder" + "github.com/runfinch/finch-daemon/internal/service/container" + "github.com/runfinch/finch-daemon/internal/service/exec" + "github.com/runfinch/finch-daemon/internal/service/image" + "github.com/runfinch/finch-daemon/internal/service/network" + "github.com/runfinch/finch-daemon/internal/service/system" + "github.com/runfinch/finch-daemon/internal/service/volume" + "github.com/runfinch/finch-daemon/pkg/archive" + "github.com/runfinch/finch-daemon/pkg/ecc" + "github.com/runfinch/finch-daemon/pkg/flog" + "github.com/spf13/afero" +) + +// handleConfigOptions gets nerdctl config value from nerdctl.toml file. +func handleConfigOptions(cfg *config.Config, options *DaemonOptions) error { + tomlPath := options.configPath + r, err := os.Open(tomlPath) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil // File not found; this is not an error. + } + return err // Return other errors directly. + } + defer r.Close() + + dec := toml.NewDecoder(r).DisallowUnknownFields() + if err := dec.Decode(cfg); err != nil { + return fmt.Errorf( + "failed to load config from %q : %w", + tomlPath, err, + ) + } + return nil +} + +// initializeConfig initializes configuration from file, environment, and set default values. +func initializeConfig(options *DaemonOptions) (*config.Config, error) { + conf := config.New() + + if err := handleConfigOptions(conf, options); err != nil { + return nil, err + } + + if options.debug { + conf.Debug = options.debug + } + if conf.Namespace == "" || conf.Namespace == namespaces.Default { + conf.Namespace = defaultNamespace + } + + return conf, nil +} + +// createNerdctlWrapper creates the Nerdctl wrapper and checks for the nerdctl binary. +func createNerdctlWrapper(clientWrapper *backend.ContainerdClientWrapper, conf *config.Config) (*backend.NerdctlWrapper, error) { + // GlobalCommandOptions is actually just an alias for Config, see + // https://github.com/containerd/nerdctl/blob/9f8655f7722d6e6851755123730436bf1a6c9995/pkg/api/types/global.go#L21 + globalOptions := (*types.GlobalCommandOptions)(conf) + ncWrapper := backend.NewNerdctlWrapper(clientWrapper, globalOptions) + if _, err := ncWrapper.GetNerdctlExe(); err != nil { + return nil, fmt.Errorf("failed to find nerdctl binary: %w", err) + } + return ncWrapper, nil +} + +// createContainerdClient creates and wraps the containerd client. +func createContainerdClient(conf *config.Config) (*backend.ContainerdClientWrapper, error) { + client, err := containerd.New(conf.Address, containerd.WithDefaultNamespace(conf.Namespace)) + if err != nil { + return nil, fmt.Errorf("failed to create containerd client: %w", err) + } + return backend.NewContainerdClientWrapper(client), nil +} + +// createRouterOptions creates router options by initializing all required services. +func createRouterOptions( + conf *config.Config, + clientWrapper *backend.ContainerdClientWrapper, + ncWrapper *backend.NerdctlWrapper, + logger *flog.Logrus, +) *router.Options { + fs := afero.NewOsFs() + tarCreator := archive.NewTarCreator(ecc.NewExecCmdCreator(), logger) + tarExtractor := archive.NewTarExtractor(ecc.NewExecCmdCreator(), logger) + + return &router.Options{ + Config: conf, + ContainerService: container.NewService(clientWrapper, ncWrapper, logger, fs, tarCreator, tarExtractor), + ImageService: image.NewService(clientWrapper, ncWrapper, logger), + NetworkService: network.NewService(clientWrapper, ncWrapper, logger), + SystemService: system.NewService(clientWrapper, ncWrapper, logger), + BuilderService: builder.NewService(clientWrapper, ncWrapper, logger, tarExtractor), + VolumeService: volume.NewService(ncWrapper, logger), + ExecService: exec.NewService(clientWrapper, logger), + NerdctlWrapper: ncWrapper, + } +} diff --git a/cmd/finch-daemon/router_utils_test.go b/cmd/finch-daemon/router_utils_test.go new file mode 100644 index 00000000..cbf8171e --- /dev/null +++ b/cmd/finch-daemon/router_utils_test.go @@ -0,0 +1,73 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "os" + "testing" + + "github.com/containerd/nerdctl/pkg/config" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestInitializeConfig(t *testing.T) { + options := &DaemonOptions{} + options.debug = true + + cfg, err := initializeConfig(options) + require.NoError(t, err, "Initialization should succeed.") + + assert.True(t, cfg.Debug, "Debug mode should be enabled.") + assert.Equal(t, "finch", defaultNamespace, "check default namespace") +} + +func TestHandleNerdctlGlobalOptions_FileNotFound(t *testing.T) { + cfg := &config.Config{} + options := &DaemonOptions{} + options.configPath = "/non/existing/path/nerdctl.toml" + + err := handleNerdctlGlobalOptions(cfg, options) + assert.NoError(t, err, "File not found should not cause an error.") +} + +func TestHandleNerdctlGlobalOptions_InvalidTOML(t *testing.T) { + cfg := &config.Config{} + options := &DaemonOptions{} + + tmpFile, err := os.CreateTemp("/tmp", "invalid.toml") + require.NoError(t, err) + + defer os.Remove(tmpFile.Name()) + + options.configPath = tmpFile.Name() + + _, _ = tmpFile.WriteString("invalid_toml") + + err = handleNerdctlGlobalOptions(cfg, options) + assert.Error(t, err, "Invalid TOML should cause an error.") +} + +func TestHandleNerdctlGlobalOptions_ValidTOML(t *testing.T) { + cfg := &config.Config{} + options := &DaemonOptions{} + + // Create a temporary valid TOML file + tmpFile, err := os.CreateTemp("", "valid.toml") + require.NoError(t, err) + + defer os.Remove(tmpFile.Name()) + + options.configPath = tmpFile.Name() + + _, _ = tmpFile.WriteString(` +address = "test_address" +namespace = "test_namespace" +`) + + err = handleNerdctlGlobalOptions(cfg, options) + assert.NoError(t, err, "Valid TOML should not cause an error.") + assert.Equal(t, "test_address", cfg.Address) + assert.Equal(t, "test_namespace", cfg.Namespace) +} diff --git a/docs/finch-daemon-config.md b/docs/finch-daemon-config.md new file mode 100644 index 00000000..4bf371a4 --- /dev/null +++ b/docs/finch-daemon-config.md @@ -0,0 +1,57 @@ + +# Configuring finch-daemon specific config + +Finch Daemon takes parameters to configure daemon specific parameters. + + **Flag** | **Description** | **Default Value** | +|---------------------|--------------------------------------------------------|--------------------------| +| `--socket-addr` | The Unix socket address where the server listens. | `/run/finch.sock` | +| `--debug` | Enable debug-level logging. | `false` | +| `--socket-owner` | Set the UID and GID of the server socket owner. | `-1` (no owner) | +| `--config-file` | Path to the daemon's configuration file (TOML format). | `/etc/finch/finch.toml` | + + +Example usage: +```bash +finch-daemon --socket-addr /tmp/finch.sock --debug --socket-owner 1001 --config-file /path/to/config.toml +``` + +# Configuring nerdctl with `finch.toml` + +Finch daemon toml config is used to configure nerdctl parameters. For more details refer to nerdctl github page. [nerdctl configuration guide](https://github.com/containerd/nerdctl/blob/main/docs/config.md). + + +## File path +- `/etc/finch/finch.toml` + +## Example + +```toml + +debug = false +debug_full = false +address = "unix:///run/k3s/containerd/containerd.sock" +namespace = "k8s.io" +snapshotter = "soci" +cgroup_manager = "cgroupfs" +hosts_dir = ["/etc/containerd/certs.d", "/etc/docker/certs.d"] +experimental = true +``` + +## Properties + +| **TOML Property** | **CLI Flag(s)** | **Description** | +|---------------------|------------------------------------------|----------------------------------------------------------------------------------------------------------------------------| +| `debug` | `--debug` | Enable debug mode. | +| `debug_full` | `--debug-full` | Enable debug mode with full output. | +| `address` | `--address`, `--host`, `-a`, `-H` | Address of the containerd daemon. | +| `namespace` | `--namespace`, `-n` | containerd namespace. | +| `snapshotter` | `--snapshotter`, `--storage-driver` | containerd snapshotter or storage driver. | +| `cni_path` | `--cni-path` | Directory containing CNI binaries. | +| `cni_netconfpath` | `--cni-netconfpath` | Directory containing CNI network configurations. | +| `data_root` | `--data-root` | Directory to store persistent state. | +| `cgroup_manager` | `--cgroup-manager` | cgroup manager to use. | +| `insecure_registry` | `--insecure-registry` | Allow communication with insecure registries. | +| `hosts_dir` | `--hosts-dir` | Directory for `certs.d` files. | +| `experimental` | `--experimental` | Enable [experimental features]. | +| `host_gateway_ip` | `--host-gateway-ip` | IP address for the special 'host-gateway' in `--add-host`. Defaults to the host IP. Has no effect without `--add-host`. | diff --git a/go.mod b/go.mod index 004c9cec..b8844b08 100644 --- a/go.mod +++ b/go.mod @@ -28,11 +28,13 @@ require ( github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0 github.com/opencontainers/runtime-spec v1.2.0 + github.com/pelletier/go-toml/v2 v2.2.3 github.com/pkg/errors v0.9.1 github.com/runfinch/common-tests v0.8.0 github.com/sirupsen/logrus v1.9.3 github.com/spf13/afero v1.11.0 github.com/spf13/cobra v1.8.1 + github.com/stretchr/testify v1.9.0 github.com/vishvananda/netlink v1.3.0 github.com/vishvananda/netns v0.0.4 golang.org/x/net v0.29.0 @@ -65,6 +67,7 @@ require ( github.com/containers/ocicrypt v1.1.10 // indirect github.com/coreos/go-iptables v0.7.0 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/djherbis/times v1.6.0 // indirect github.com/docker/docker-credential-helpers v0.8.1 // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect @@ -119,6 +122,7 @@ require ( github.com/opencontainers/selinux v1.11.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/philhofer/fwd v1.1.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rootless-containers/bypass4netns v0.4.0 // indirect github.com/rootless-containers/rootlesskit v1.1.1 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect diff --git a/go.sum b/go.sum index 343223ab..fc27189a 100644 --- a/go.sum +++ b/go.sum @@ -263,6 +263,8 @@ github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaL github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -295,8 +297,9 @@ github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 h1:pnnLy github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6/go.mod h1:39R/xuhNgVhi+K0/zst4TLrJrVmbm6LVgl4A0+ZFS5M= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=