Skip to content

Commit

Permalink
feat: Add support for nerdctl config and default variables
Browse files Browse the repository at this point in the history
Signed-off-by: Shubharanshu Mahapatra <[email protected]>
  • Loading branch information
Shubhranshu153 committed Oct 27, 2024
1 parent ad26173 commit 7d0fc13
Show file tree
Hide file tree
Showing 5 changed files with 279 additions and 44 deletions.
57 changes: 14 additions & 43 deletions cmd/finch-daemon/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,12 @@ import (
"syscall"
"time"

"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 (
Expand Down Expand Up @@ -122,36 +107,22 @@ func run(options *DaemonOptions) error {
}

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))
conf, err := initializeConfig(debug)
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
}

Expand Down
155 changes: 155 additions & 0 deletions cmd/finch-daemon/router_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package main

import (
"errors"
"fmt"
"os"
"strconv"

"github.com/containerd/containerd"
"github.com/containerd/containerd/namespaces"
"github.com/containerd/nerdctl/pkg/api/types"
"github.com/containerd/nerdctl/pkg/config"
ncdefaults "github.com/containerd/nerdctl/pkg/defaults"
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"
)

// handleNerdctlGlobalOptions gets nerdctl config value from nerdctl.toml file.
func handleNerdctlGlobalOptions(cfg *config.Config) error {
tomlPath := getTomlPath()
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 nerdctl config from %q (Note: This is referring to `nerdctl.toml`): %w",
tomlPath, err,
)
}
return nil
}

// handleNerdctlGlobalOptionsEnvVariable configures nerdctl config values with env values.
func handleNerdctlGlobalOptionsEnvVariable(cfg *config.Config) error {
envVars := map[string]*string{
"CONTAINERD_ADDRESS": &cfg.Address,
"CONTAINERD_NAMESPACE": &cfg.Namespace,
"CONTAINERD_SNAPSHOTTER": &cfg.Snapshotter,
"CNI_PATH": &cfg.CNIPath,
"NETCONFPATH": &cfg.CNINetConfPath,
"NERDCTL_HOST_GATEWAY_IP": &cfg.HostGatewayIP,
}

for env, field := range envVars {
if value, ok := os.LookupEnv(env); ok {
*field = value
}
}

if value, ok := os.LookupEnv("NERDCTL_EXPERIMENTAL"); ok {
experimental, err := strconv.ParseBool(value)
if err != nil {
return err
}
cfg.Experimental = experimental
}
return nil
}

// getTomlPath retrieves the TOML configuration path.
func getTomlPath() string {
if v, ok := os.LookupEnv("NERDCTL_TOML"); ok {
return v
}
return ncdefaults.NerdctlTOML()
}

// initializeConfig initializes configuration from file, environment, and set default values.
func initializeConfig(debug bool) (*config.Config, error) {
conf := config.New()
conf.Debug = debug
conf.Namespace = defaultNamespace

if err := handleNerdctlGlobalOptions(conf); err != nil {
return nil, err
}

if err := handleNerdctlGlobalOptionsEnvVariable(conf); err != nil {
return nil, err
}

conf.Debug = 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,
}
}
102 changes: 102 additions & 0 deletions cmd/finch-daemon/router_utils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package main

import (
"os"
"strings"
"testing"

"github.com/containerd/nerdctl/pkg/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestHandleNerdctlGlobalOptionsEnvVariable(t *testing.T) {
cfg := &config.Config{}

// Set mock environment variables.
os.Setenv("CONTAINERD_ADDRESS", "test_address")
defer os.Unsetenv("CONTAINERD_ADDRESS")
os.Setenv("CONTAINERD_NAMESPACE", "test_namespace")
defer os.Unsetenv("CONTAINERD_NAMESPACE")
os.Setenv("NERDCTL_EXPERIMENTAL", "true")
defer os.Unsetenv("NERDCTL_EXPERIMENTAL")

err := handleNerdctlGlobalOptionsEnvVariable(cfg)
assert.NoError(t, err, "Valid environment variables should not cause an error.")
assert.Equal(t, "test_address", cfg.Address)
assert.Equal(t, "test_namespace", cfg.Namespace)
assert.True(t, cfg.Experimental)
}

func TestHandleNerdctlGlobalOptionsEnvVariable_InvalidBool(t *testing.T) {
cfg := &config.Config{}

os.Setenv("NERDCTL_EXPERIMENTAL", "invalid")
defer os.Unsetenv("NERDCTL_EXPERIMENTAL")

err := handleNerdctlGlobalOptionsEnvVariable(cfg)
assert.Error(t, err, "Invalid boolean environment variable should cause an error.")
}

func TestInitializeConfig(t *testing.T) {

os.Setenv("NERDCTL_TOML", "/non/existing/path/nerdctl.toml")
os.Setenv("CONTAINERD_NAMESPACE", "test_namespace")
defer os.Unsetenv("CONTAINERD_NAMESPACE")
defer os.Unsetenv("NERDCTL_TOML")

cfg, err := initializeConfig(true)
require.NoError(t, err, "Initialization should succeed.")

assert.True(t, cfg.Debug, "Debug mode should be enabled.")
assert.Equal(t, "test_namespace", cfg.Namespace, "Namespace should be set from environment variable.")
}

func TestHandleNerdctlGlobalOptions_FileNotFound(t *testing.T) {
cfg := &config.Config{}
os.Setenv("NERDCTL_TOML", "/non/existing/path/nerdctl.toml")
defer os.Unsetenv("NERDCTL_TOML")

err := handleNerdctlGlobalOptions(cfg)
assert.NoError(t, err, "File not found should not cause an error.")
}

func TestHandleNerdctlGlobalOptions_InvalidTOML(t *testing.T) {
cfg := &config.Config{}

tmpFile, err := os.CreateTemp("/tmp", "invalid.toml")
os.Setenv("NERDCTL_TOML", tmpFile.Name())
defer os.Unsetenv("NERDCTL_TOML")
require.NoError(t, err)
defer os.Remove(tmpFile.Name())

_, _ = tmpFile.WriteString("invalid_toml")

err = handleNerdctlGlobalOptions(cfg)
assert.Error(t, err, "Invalid TOML should return an error.")
assert.True(t, strings.Contains(err.Error(), "failed to load nerdctl config"))
}

func TestHandleNerdctlGlobalOptions_ValidTOML(t *testing.T) {
cfg := &config.Config{}

// Create a temporary valid TOML file
tmpFile, err := os.CreateTemp("", "valid.toml")
os.Setenv("NERDCTL_TOML", tmpFile.Name())
defer os.Unsetenv("NERDCTL_TOML")
require.NoError(t, err)
defer os.Remove(tmpFile.Name())

_, _ = tmpFile.WriteString(`
address = "test_address"
namespace = "test_namespace"
`)

err = handleNerdctlGlobalOptions(cfg)
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)
}
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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.7.21
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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down

0 comments on commit 7d0fc13

Please sign in to comment.