Skip to content

Commit

Permalink
support default platform config in nerdctl.toml
Browse files Browse the repository at this point in the history
Signed-off-by: YangKian <1207783292@qq.com>
  • Loading branch information
YangKian committed Aug 31, 2022
1 parent 4d344cf commit ad0f6d9
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 31 deletions.
6 changes: 4 additions & 2 deletions cmd/nerdctl/build.go
Expand Up @@ -26,6 +26,8 @@ import (
"strconv"
"strings"

"github.com/compose-spec/compose-go/types"

"path/filepath"

"github.com/containerd/containerd/errdefs"
Expand All @@ -39,7 +41,7 @@ import (
"github.com/spf13/cobra"
)

func newBuildCommand() *cobra.Command {
func newBuildCommand(cfg *types.BuildConfig) *cobra.Command {
var buildCommand = &cobra.Command{
Use: "build",
Short: "Build an image from a Dockerfile. Needs buildkitd to be running.",
Expand All @@ -66,7 +68,7 @@ If Dockerfile is not present and -f is not specified, it will look for Container

// #region platform flags
// platform is defined as StringSlice, not StringArray, to allow specifying "--platform=amd64,arm64"
buildCommand.Flags().StringSlice("platform", []string{}, "Set target platform for build (e.g., \"amd64\", \"arm64\")")
buildCommand.Flags().StringSlice("platform", cfg.Platforms, "Set target platform for build (e.g., \"amd64\", \"arm64\")")
buildCommand.RegisterFlagCompletionFunc("platform", shellCompletePlatforms)
// #endregion

Expand Down
4 changes: 3 additions & 1 deletion cmd/nerdctl/container.go
Expand Up @@ -29,9 +29,11 @@ func newContainerCommand() *cobra.Command {
SilenceUsage: true,
SilenceErrors: true,
}

cfg := defaultConfigTemplate()
containerCommand.AddCommand(
newCreateCommand(),
newRunCommand(),
newRunCommand(&cfg),
newUpdateCommand(),
newExecCommand(),
containerLsCommand(),
Expand Down
3 changes: 2 additions & 1 deletion cmd/nerdctl/create.go
Expand Up @@ -45,7 +45,8 @@ func newCreateCommand() *cobra.Command {
SilenceErrors: true,
}
createCommand.Flags().SetInterspersed(false)
setCreateFlags(createCommand)
cfg := defaultConfigTemplate()
setCreateFlags(createCommand, &cfg)
return createCommand
}

Expand Down
3 changes: 2 additions & 1 deletion cmd/nerdctl/image.go
Expand Up @@ -29,8 +29,9 @@ func newImageCommand() *cobra.Command {
SilenceUsage: true,
SilenceErrors: true,
}
cfg := defaultConfigTemplate()
cmd.AddCommand(
newBuildCommand(),
newBuildCommand(cfg.Build),
// commitCommand is in "container", not in "image"
imageLsCommand(),
newHistoryCommand(),
Expand Down
100 changes: 81 additions & 19 deletions cmd/nerdctl/main.go
Expand Up @@ -24,6 +24,9 @@ import (
"strconv"
"strings"

"github.com/compose-spec/compose-go/types"
"github.com/containerd/nerdctl/pkg/reflectutil"

"github.com/containerd/containerd"
"github.com/containerd/containerd/defaults"
"github.com/containerd/containerd/namespaces"
Expand All @@ -32,7 +35,6 @@ import (
"github.com/containerd/nerdctl/pkg/rootlessutil"
"github.com/containerd/nerdctl/pkg/version"
"github.com/pelletier/go-toml"

"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
Expand Down Expand Up @@ -88,18 +90,67 @@ func xmain() error {
// Config corresponds to nerdctl.toml .
// See docs/config.md .
type Config struct {
Debug bool `toml:"debug"`
DebugFull bool `toml:"debug_full"`
Address string `toml:"address"`
Namespace string `toml:"namespace"`
Snapshotter string `toml:"snapshotter"`
CNIPath string `toml:"cni_path"`
CNINetConfPath string `toml:"cni_netconfpath"`
DataRoot string `toml:"data_root"`
CgroupManager string `toml:"cgroup_manager"`
InsecureRegistry bool `toml:"insecure_registry"`
HostsDir []string `toml:"hosts_dir"`
Debug bool `toml:"debug"`
DebugFull bool `toml:"debug_full"`
Address string `toml:"address"`
Namespace string `toml:"namespace"`
Snapshotter string `toml:"snapshotter"`
CNIPath string `toml:"cni_path"`
CNINetConfPath string `toml:"cni_netconfpath"`
DataRoot string `toml:"data_root"`
CgroupManager string `toml:"cgroup_manager"`
InsecureRegistry bool `toml:"insecure_registry"`
HostsDir []string `toml:"hosts_dir"`
Experimental bool `toml:"experimental"`
DefaultConfig ConfigTemplates `toml:"default_config"`
}

type ConfigTemplates map[string]types.ServiceConfig

func (d *ConfigTemplates) UnmarshalTOML(v interface{}) error {
res := make(map[string]types.ServiceConfig)
for namespace, vl := range v.(map[string]interface{}) {
dcfg := defaultConfigTemplate()
tree, err := toml.TreeFromMap(vl.(map[string]interface{}))
if err != nil {
return err
}
if err = tree.Unmarshal(&dcfg); err != nil {
return err
}
warnUnknownFields(dcfg, namespace)
res[namespace] = dcfg
}
*d = res
return nil
}

func defaultConfigTemplate() types.ServiceConfig {
return types.ServiceConfig{
Build: &types.BuildConfig{
Platforms: []string{},
},
Platform: "",
Net: "bridge",
}
}

func warnUnknownFields(svc types.ServiceConfig, namespace string) {
if unknown := reflectutil.UnknownNonEmptyFields(&svc,
"Net",
"Build",
"Platform",
); len(unknown) > 0 {
logrus.Warnf("Ignoring: [namespace %s] service %s: %+v", namespace, svc.Name, unknown)
}

if svc.Build != nil {
if unknown := reflectutil.UnknownNonEmptyFields(svc.Build,
"Platforms",
); len(unknown) > 0 {
logrus.Warnf("Ignoring: [namespace %s] service %s: build: %+v", namespace, svc.Name, unknown)
}
}
}

// NewConfig creates a default Config object statically,
Expand All @@ -118,23 +169,24 @@ func NewConfig() *Config {
InsecureRegistry: false,
HostsDir: ncdefaults.HostsDirs(),
Experimental: true,
DefaultConfig: make(map[string]types.ServiceConfig),
}
}

func initRootCmdFlags(rootCmd *cobra.Command, tomlPath string) error {
func initRootCmdFlags(rootCmd *cobra.Command, tomlPath string) (*Config, error) {
cfg := NewConfig()
if r, err := os.Open(tomlPath); err == nil {
logrus.Debugf("Loading config from %q", tomlPath)
defer r.Close()
dec := toml.NewDecoder(r).Strict(true) // set Strict to detect typo
if err := dec.Decode(cfg); err != nil {
return fmt.Errorf("failed to load nerdctl config (not daemon config) from %q (Hint: don't mix up daemon's `config.toml` with `nerdctl.toml`): %w", tomlPath, err)
return nil, fmt.Errorf("failed to load nerdctl config (not daemon config) from %q (Hint: don't mix up daemon's `config.toml` with `nerdctl.toml`): %w", tomlPath, err)
}
logrus.Debugf("Loaded config %+v", cfg)
} else {
logrus.WithError(err).Debugf("Not loading config from %q", tomlPath)
if !errors.Is(err, os.ErrNotExist) {
return err
return nil, err
}
}
rootCmd.PersistentFlags().Bool("debug", cfg.Debug, "debug mode")
Expand All @@ -157,7 +209,7 @@ func initRootCmdFlags(rootCmd *cobra.Command, tomlPath string) error {
rootCmd.PersistentFlags().StringSlice("hosts-dir", cfg.HostsDir, "A directory that contains <HOST:PORT>/hosts.toml (containerd style) or <HOST:PORT>/{ca.cert, cert.pem, key.pem} (docker style)")
// Experimental enable experimental feature, see in https://github.com/containerd/nerdctl/blob/master/docs/experimental.md
AddPersistentBoolFlag(rootCmd, "experimental", nil, nil, cfg.Experimental, "NERDCTL_EXPERIMENTAL", "Control experimental: https://github.com/containerd/nerdctl/blob/master/docs/experimental.md")
return nil
return cfg, nil
}

func newApp() (*cobra.Command, error) {
Expand All @@ -181,10 +233,20 @@ Config file ($NERDCTL_TOML): %s
TraverseChildren: true, // required for global short hands like -a, -H, -n
}
rootCmd.SetUsageTemplate(mainHelpTemplate)
if err := initRootCmdFlags(rootCmd, tomlPath); err != nil {
cfg, err := initRootCmdFlags(rootCmd, tomlPath)
if err != nil {
return nil, err
}

namespaceCfg, ok := cfg.DefaultConfig[cfg.Namespace]
if !ok {
namespaceCfg = defaultConfigTemplate()
}
size := len(namespaceCfg.Build.Platforms)
if size != 0 && namespaceCfg.Platform != namespaceCfg.Build.Platforms[size-1] {
return nil, fmt.Errorf("conflict platform config")
}

rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
debug, err := cmd.Flags().GetBool("debug-full")
if err != nil {
Expand Down Expand Up @@ -224,7 +286,7 @@ Config file ($NERDCTL_TOML): %s
rootCmd.AddCommand(
newCreateCommand(),
// #region Run & Exec
newRunCommand(),
newRunCommand(&namespaceCfg),
newUpdateCommand(),
newExecCommand(),
// #endregion
Expand All @@ -246,7 +308,7 @@ Config file ($NERDCTL_TOML): %s
// #endregion

// Build
newBuildCommand(),
newBuildCommand(namespaceCfg.Build),

// #region Image management
newImagesCommand(),
Expand Down
51 changes: 51 additions & 0 deletions cmd/nerdctl/main_test.go
Expand Up @@ -19,9 +19,12 @@ package main
import (
"os"
"path/filepath"
"strings"
"testing"

"github.com/containerd/containerd"
ncdefaults "github.com/containerd/nerdctl/pkg/defaults"
"github.com/containerd/nerdctl/pkg/platformutil"
"github.com/containerd/nerdctl/pkg/testutil"
"gotest.tools/v3/assert"
)
Expand Down Expand Up @@ -88,3 +91,51 @@ version = 2
base.Env = append(base.Env, "NERDCTL_TOML="+tomlPath)
base.Cmd("info").AssertFail()
}

// TestNerdctlConditionalConfig validates the execution of conditional configuration options [CLI, TOML, Default].
func TestNerdctlConditionalConfig(t *testing.T) {
testutil.DockerIncompatible(t)
t.Parallel()
tomlPath := ncdefaults.NerdctlTOML()
err := os.MkdirAll(filepath.Dir(tomlPath), 0755)
assert.NilError(t, err)
defer func(path string) {
_ = os.Remove(path)
}(tomlPath)
err = os.WriteFile(tomlPath, []byte(`
[default_run_config]
platform = "arm64"
`), 0400)
assert.NilError(t, err)
base := testutil.NewBase(t)
testutil.RequireExecPlatform(t, "linux/amd64", "linux/arm64")

// [DEFAULT]
platformAmd64, err := platformutil.NormalizeString("amd64")
assert.NilError(t, err)
base.Cmd("run", testutil.CommonImage, "uname", "-m").AssertOutWithFunc(checkPlatForm(t, platformAmd64))

// [CLI, DEFAULT]
platformArm64, err := platformutil.NormalizeString("arm64")
assert.NilError(t, err)
base.Cmd("run", "--platform", "arm64", "--rm", testutil.CommonImage, "uname", "-m").AssertOutWithFunc(checkPlatForm(t, platformArm64))

// [TOML, DEFAULT]
if len(base.Env) == 0 {
base.Env = os.Environ()
}
base.Env = append(base.Env, "NERDCTL_TOML="+tomlPath)
base.Cmd("run", "--rm", testutil.CommonImage, "uname", "-m").AssertOutWithFunc(checkPlatForm(t, platformArm64))

// [CLI, TOML, DEFAULT]
base.Cmd("run", "--platform", "amd64", "--rm", testutil.CommonImage, "uname", "-m").AssertOutWithFunc(checkPlatForm(t, platformAmd64))
}

func checkPlatForm(t *testing.T, target string) func(out string) error {
return func(out string) error {
platformOut, err := platformutil.NormalizeString(strings.TrimSuffix(out, "\n"))
assert.NilError(t, err)
assert.Equal(t, target, platformOut)
return nil
}
}
14 changes: 8 additions & 6 deletions cmd/nerdctl/run.go
Expand Up @@ -31,6 +31,9 @@ import (
"strconv"
"strings"

"github.com/compose-spec/compose-go/types"
"github.com/containerd/nerdctl/pkg/netutil"

"github.com/containerd/console"
"github.com/containerd/containerd"
"github.com/containerd/containerd/cio"
Expand All @@ -47,7 +50,6 @@ import (
"github.com/containerd/nerdctl/pkg/logging"
"github.com/containerd/nerdctl/pkg/mountutil"
"github.com/containerd/nerdctl/pkg/namestore"
"github.com/containerd/nerdctl/pkg/netutil"
"github.com/containerd/nerdctl/pkg/platformutil"
"github.com/containerd/nerdctl/pkg/referenceutil"
"github.com/containerd/nerdctl/pkg/strutil"
Expand All @@ -62,7 +64,7 @@ const (
tiniInitBinary = "tini"
)

func newRunCommand() *cobra.Command {
func newRunCommand(cfg *types.ServiceConfig) *cobra.Command {
shortHelp := "Run a command in a new container. Optionally specify \"ipfs://\" or \"ipns://\" scheme to pull image from IPFS."
longHelp := shortHelp
switch runtime.GOOS {
Expand All @@ -85,14 +87,14 @@ func newRunCommand() *cobra.Command {
}

runCommand.Flags().SetInterspersed(false)
setCreateFlags(runCommand)
setCreateFlags(runCommand, cfg)

runCommand.Flags().BoolP("detach", "d", false, "Run container in background and print container ID")

return runCommand
}

func setCreateFlags(cmd *cobra.Command) {
func setCreateFlags(cmd *cobra.Command, cfg *types.ServiceConfig) {

// No "-h" alias for "--help", because "-h" for "--hostname".
cmd.Flags().Bool("help", false, "show help")
Expand All @@ -117,13 +119,13 @@ func setCreateFlags(cmd *cobra.Command) {
// #endregion

// #region platform flags
cmd.Flags().String("platform", "", "Set platform (e.g. \"amd64\", \"arm64\")") // not a slice, and there is no --all-platforms
cmd.Flags().String("platform", cfg.Platform, "Set platform (e.g. \"amd64\", \"arm64\")") // not a slice, and there is no --all-platforms
cmd.RegisterFlagCompletionFunc("platform", shellCompletePlatforms)
// #endregion

// #region network flags
// network (net) is defined as StringSlice, not StringArray, to allow specifying "--network=cni1,cni2"
cmd.Flags().StringSlice("network", []string{netutil.DefaultNetworkName}, `Connect a container to a network ("bridge"|"host"|"none"|"container:<container>"|<CNI>)`)
cmd.Flags().StringSlice("network", []string{cfg.Net}, `Connect a container to a network ("bridge"|"host"|"none"|"container:<container>"|<CNI>)`)
cmd.RegisterFlagCompletionFunc("network", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return shellCompleteNetworkNames(cmd, []string{})
})
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Expand Up @@ -143,7 +143,7 @@ require (
github.com/opencontainers/selinux v1.10.1 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/philhofer/fwd v1.1.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/errors v0.9.1
github.com/polydawn/refmt v0.0.0-20201211092308-30ac6d18308e // indirect
github.com/prometheus/client_golang v1.12.2 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
Expand Down

0 comments on commit ad0f6d9

Please sign in to comment.