diff --git a/.gitignore b/.gitignore index f766658d..3cf3c7a8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ dist *.swp *.swo -/coverage.out \ No newline at end of file +/coverage.out +/test/output/ +/nvidia-container-runtime +/nvidia-container-toolkit diff --git a/Makefile b/Makefile index d262f2e4..27d414c8 100644 --- a/Makefile +++ b/Makefile @@ -17,8 +17,8 @@ MKDIR ?= mkdir DIST_DIR ?= $(CURDIR)/dist LIB_NAME := nvidia-container-toolkit -LIB_VERSION := 1.5.1 -LIB_TAG ?= +LIB_VERSION := 1.5.2 +LIB_TAG ?= rc.1 GOLANG_VERSION := 1.16.3 MODULE := github.com/NVIDIA/nvidia-container-toolkit @@ -37,18 +37,28 @@ BUILDIMAGE ?= $(IMAGE):$(IMAGE_TAG)-devel EXAMPLES := $(patsubst ./examples/%/,%,$(sort $(dir $(wildcard ./examples/*/)))) EXAMPLE_TARGETS := $(patsubst %,example-%, $(EXAMPLES)) -CHECK_TARGETS := assert-fmt vet lint ineffassign misspell -MAKE_TARGETS := binary build all check fmt lint-internal test examples coverage generate $(CHECK_TARGETS) +CMDS := $(patsubst ./cmd/%/,%,$(sort $(dir $(wildcard ./cmd/*/)))) +CMD_TARGETS := $(patsubst %,cmd-%, $(CMDS)) -TARGETS := $(MAKE_TARGETS) $(EXAMPLE_TARGETS) +$(info CMD_TARGETS=$(CMD_TARGETS)) + +CHECK_TARGETS := assert-fmt vet lint ineffassign misspell +MAKE_TARGETS := binaries build all check fmt lint-internal test examples cmds coverage generate $(CHECK_TARGETS) + +TARGETS := $(MAKE_TARGETS) $(EXAMPLE_TARGETS) $(CMD_TARGETS) DOCKER_TARGETS := $(patsubst %,docker-%, $(TARGETS)) .PHONY: $(TARGETS) $(DOCKER_TARGETS) GOOS ?= linux -binary: - GOOS=$(GOOS) go build -ldflags "-s -w" -o "$(LIB_NAME)" $(MODULE)/cmd/$(LIB_NAME) +binaries: cmds +ifneq ($(PREFIX),) +cmd-%: COMMAND_BUILD_OPTIONS = -o $(PREFIX)/$(*) +endif +cmds: $(CMD_TARGETS) +$(CMD_TARGETS): cmd-%: + GOOS=$(GOOS) go build -ldflags "-s -w" $(COMMAND_BUILD_OPTIONS) $(MODULE)/cmd/$(*) build: GOOS=$(GOOS) go build ./... @@ -95,7 +105,7 @@ vet: go vet $(MODULE)/... COVERAGE_FILE := coverage.out -test: build +test: build cmds go test -v -coverprofile=$(COVERAGE_FILE) $(MODULE)/... coverage: test diff --git a/cmd/nvidia-container-runtime/logger.go b/cmd/nvidia-container-runtime/logger.go new file mode 100644 index 00000000..803a1d35 --- /dev/null +++ b/cmd/nvidia-container-runtime/logger.go @@ -0,0 +1,79 @@ +/* +# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +*/ + +package main + +import ( + "fmt" + "io" + "os" + + "github.com/sirupsen/logrus" + "github.com/tsaikd/KDGoLib/logrusutil" +) + +// Logger adds a way to manage output to a log file to a logrus.Logger +type Logger struct { + *logrus.Logger + previousOutput io.Writer + logFile *os.File +} + +// NewLogger constructs a Logger with a preddefined formatter +func NewLogger() *Logger { + logrusLogger := logrus.New() + + formatter := &logrusutil.ConsoleLogFormatter{ + TimestampFormat: "2006/01/02 15:04:07", + Flag: logrusutil.Ltime, + } + + logger := &Logger{ + Logger: logrusLogger, + } + logger.SetFormatter(formatter) + + return logger +} + +// LogToFile opens the specified file for appending and sets the logger to +// output to the opened file. A reference to the file pointer is stored to +// allow this to be closed. +func (l *Logger) LogToFile(filename string) error { + logFile, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return fmt.Errorf("error opening debug log file: %v", err) + } + + l.logFile = logFile + l.previousOutput = l.Out + l.SetOutput(logFile) + + return nil +} + +// CloseFile closes the log file (if any) and resets the logger output to what it +// was before LogToFile was called. +func (l *Logger) CloseFile() error { + if l.logFile == nil { + return nil + } + logFile := l.logFile + l.SetOutput(l.previousOutput) + l.logFile = nil + + return logFile.Close() +} diff --git a/cmd/nvidia-container-runtime/main.go b/cmd/nvidia-container-runtime/main.go new file mode 100644 index 00000000..cbb52b71 --- /dev/null +++ b/cmd/nvidia-container-runtime/main.go @@ -0,0 +1,89 @@ +package main + +import ( + "fmt" + "os" + "path" + + "github.com/pelletier/go-toml" +) + +const ( + configOverride = "XDG_CONFIG_HOME" + configFilePath = "nvidia-container-runtime/config.toml" + + hookDefaultFilePath = "/usr/bin/nvidia-container-runtime-hook" +) + +var ( + configDir = "/etc/" +) + +var logger = NewLogger() + +func main() { + err := run(os.Args) + if err != nil { + logger.Errorf("Error running %v: %v", os.Args, err) + os.Exit(1) + } +} + +// run is an entry point that allows for idiomatic handling of errors +// when calling from the main function. +func run(argv []string) (err error) { + cfg, err := getConfig() + if err != nil { + return fmt.Errorf("error loading config: %v", err) + } + + err = logger.LogToFile(cfg.debugFilePath) + if err != nil { + return fmt.Errorf("error opening debug log file: %v", err) + } + defer func() { + // We capture and log a returning error before closing the log file. + if err != nil { + logger.Errorf("Error running %v: %v", argv, err) + } + logger.CloseFile() + }() + + r, err := newRuntime(argv) + if err != nil { + return fmt.Errorf("error creating runtime: %v", err) + } + + logger.Printf("Running %s\n", argv[0]) + return r.Exec(argv) +} + +type config struct { + debugFilePath string +} + +// getConfig sets up the config struct. Values are read from a toml file +// or set via the environment. +func getConfig() (*config, error) { + cfg := &config{} + + if XDGConfigDir := os.Getenv(configOverride); len(XDGConfigDir) != 0 { + configDir = XDGConfigDir + } + + configFilePath := path.Join(configDir, configFilePath) + + tomlContent, err := os.ReadFile(configFilePath) + if err != nil { + return nil, err + } + + toml, err := toml.Load(string(tomlContent)) + if err != nil { + return nil, err + } + + cfg.debugFilePath = toml.GetDefault("nvidia-container-runtime.debug", "/dev/null").(string) + + return cfg, nil +} diff --git a/cmd/nvidia-container-runtime/main_test.go b/cmd/nvidia-container-runtime/main_test.go new file mode 100644 index 00000000..9ef83b9f --- /dev/null +++ b/cmd/nvidia-container-runtime/main_test.go @@ -0,0 +1,293 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "testing" + + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/stretchr/testify/require" +) + +const ( + nvidiaRuntime = "nvidia-container-runtime" + nvidiaHook = "nvidia-container-runtime-hook" + bundlePathSuffix = "test/output/bundle/" + specFile = "config.json" + unmodifiedSpecFileSuffix = "test/input/test_spec.json" +) + +type testConfig struct { + root string + binPath string +} + +var cfg *testConfig + +func TestMain(m *testing.M) { + // TEST SETUP + // Determine the module root and the test binary path + var err error + moduleRoot, err := getModuleRoot() + if err != nil { + logger.Fatalf("error in test setup: could not get module root: %v", err) + } + testBinPath := filepath.Join(moduleRoot, "test", "bin") + testInputPath := filepath.Join(moduleRoot, "test", "input") + + // Set the environment variables for the test + os.Setenv("PATH", prependToPath(testBinPath, moduleRoot)) + os.Setenv("XDG_CONFIG_HOME", testInputPath) + + // Confirm that the environment is configured correctly + runcPath, err := exec.LookPath(runcExecutableName) + if err != nil || filepath.Join(testBinPath, runcExecutableName) != runcPath { + logger.Fatalf("error in test setup: mock runc path set incorrectly in TestMain(): %v", err) + } + hookPath, err := exec.LookPath(nvidiaHook) + if err != nil || filepath.Join(testBinPath, nvidiaHook) != hookPath { + logger.Fatalf("error in test setup: mock hook path set incorrectly in TestMain(): %v", err) + } + + // Store the root and binary paths in the test Config + cfg = &testConfig{ + root: moduleRoot, + binPath: testBinPath, + } + + // RUN TESTS + exitCode := m.Run() + + // TEST CLEANUP + os.Remove(specFile) + + os.Exit(exitCode) +} + +func getModuleRoot() (string, error) { + _, filename, _, _ := runtime.Caller(0) + + return hasGoMod(filename) +} + +func hasGoMod(dir string) (string, error) { + if dir == "" || dir == "/" { + return "", fmt.Errorf("module root not found") + } + + _, err := os.Stat(filepath.Join(dir, "go.mod")) + if err != nil { + return hasGoMod(filepath.Dir(dir)) + } + return dir, nil +} + +func prependToPath(additionalPaths ...string) string { + paths := strings.Split(os.Getenv("PATH"), ":") + paths = append(additionalPaths, paths...) + + return strings.Join(paths, ":") +} + +// case 1) nvidia-container-runtime run --bundle +// case 2) nvidia-container-runtime create --bundle +// - Confirm the runtime handles bad input correctly +func TestBadInput(t *testing.T) { + err := cfg.generateNewRuntimeSpec() + if err != nil { + t.Fatal(err) + } + + cmdRun := exec.Command(nvidiaRuntime, "run", "--bundle") + t.Logf("executing: %s\n", strings.Join(cmdRun.Args, " ")) + output, err := cmdRun.CombinedOutput() + require.Errorf(t, err, "runtime should return an error", "output=%v", string(output)) + + cmdCreate := exec.Command(nvidiaRuntime, "create", "--bundle") + t.Logf("executing: %s\n", strings.Join(cmdCreate.Args, " ")) + err = cmdCreate.Run() + require.Error(t, err, "runtime should return an error") +} + +// case 1) nvidia-container-runtime run --bundle +// - Confirm the runtime runs with no errors +// case 2) nvidia-container-runtime create --bundle +// - Confirm the runtime inserts the NVIDIA prestart hook correctly +func TestGoodInput(t *testing.T) { + err := cfg.generateNewRuntimeSpec() + if err != nil { + t.Fatalf("error generating runtime spec: %v", err) + } + + cmdRun := exec.Command(nvidiaRuntime, "run", "--bundle", cfg.bundlePath(), "testcontainer") + t.Logf("executing: %s\n", strings.Join(cmdRun.Args, " ")) + output, err := cmdRun.CombinedOutput() + require.NoErrorf(t, err, "runtime should not return an error", "output=%v", string(output)) + + // Check config.json and confirm there are no hooks + spec, err := cfg.getRuntimeSpec() + require.NoError(t, err, "should be no errors when reading and parsing spec from config.json") + require.Empty(t, spec.Hooks, "there should be no hooks in config.json") + + cmdCreate := exec.Command(nvidiaRuntime, "create", "--bundle", cfg.bundlePath(), "testcontainer") + t.Logf("executing: %s\n", strings.Join(cmdCreate.Args, " ")) + err = cmdCreate.Run() + require.NoError(t, err, "runtime should not return an error") + + // Check config.json for NVIDIA prestart hook + spec, err = cfg.getRuntimeSpec() + require.NoError(t, err, "should be no errors when reading and parsing spec from config.json") + require.NotEmpty(t, spec.Hooks, "there should be hooks in config.json") + require.Equal(t, 1, nvidiaHookCount(spec.Hooks), "exactly one nvidia prestart hook should be inserted correctly into config.json") +} + +// NVIDIA prestart hook already present in config file +func TestDuplicateHook(t *testing.T) { + err := cfg.generateNewRuntimeSpec() + if err != nil { + t.Fatal(err) + } + + var spec specs.Spec + spec, err = cfg.getRuntimeSpec() + if err != nil { + t.Fatal(err) + } + + t.Logf("inserting nvidia prestart hook to config.json") + if err = addNVIDIAHook(&spec); err != nil { + t.Fatal(err) + } + + jsonOutput, err := json.MarshalIndent(spec, "", "\t") + if err != nil { + t.Fatal(err) + } + + jsonFile, err := os.OpenFile(cfg.specFilePath(), os.O_RDWR, 0644) + if err != nil { + t.Fatal(err) + } + _, err = jsonFile.WriteAt(jsonOutput, 0) + if err != nil { + t.Fatal(err) + } + + // Test how runtime handles already existing prestart hook in config.json + cmdCreate := exec.Command(nvidiaRuntime, "create", "--bundle", cfg.bundlePath(), "testcontainer") + t.Logf("executing: %s\n", strings.Join(cmdCreate.Args, " ")) + output, err := cmdCreate.CombinedOutput() + require.NoErrorf(t, err, "runtime should not return an error", "output=%v", string(output)) + + // Check config.json for NVIDIA prestart hook + spec, err = cfg.getRuntimeSpec() + require.NoError(t, err, "should be no errors when reading and parsing spec from config.json") + require.NotEmpty(t, spec.Hooks, "there should be hooks in config.json") + require.Equal(t, 1, nvidiaHookCount(spec.Hooks), "exactly one nvidia prestart hook should be inserted correctly into config.json") +} + +// addNVIDIAHook is a basic wrapper for nvidiaContainerRunime.addNVIDIAHook that is used for +// testing. +func addNVIDIAHook(spec *specs.Spec) error { + r := nvidiaContainerRuntime{logger: logger.Logger} + return r.addNVIDIAHook(spec) +} + +func (c testConfig) getRuntimeSpec() (specs.Spec, error) { + filePath := c.specFilePath() + + var spec specs.Spec + jsonFile, err := os.OpenFile(filePath, os.O_RDWR, 0644) + if err != nil { + return spec, err + } + defer jsonFile.Close() + + jsonContent, err := ioutil.ReadAll(jsonFile) + if err != nil { + return spec, err + } else if json.Valid(jsonContent) { + err = json.Unmarshal(jsonContent, &spec) + if err != nil { + return spec, err + } + } else { + err = json.NewDecoder(bytes.NewReader(jsonContent)).Decode(&spec) + if err != nil { + return spec, err + } + } + + return spec, err +} + +func (c testConfig) bundlePath() string { + return filepath.Join(c.root, bundlePathSuffix) +} + +func (c testConfig) specFilePath() string { + return filepath.Join(c.bundlePath(), specFile) +} + +func (c testConfig) unmodifiedSpecFile() string { + return filepath.Join(c.root, unmodifiedSpecFileSuffix) +} + +func (c testConfig) generateNewRuntimeSpec() error { + var err error + + err = os.MkdirAll(c.bundlePath(), 0755) + if err != nil { + return err + } + + cmd := exec.Command("cp", c.unmodifiedSpecFile(), c.specFilePath()) + err = cmd.Run() + if err != nil { + return err + } + return nil +} + +// Return number of valid NVIDIA prestart hooks in runtime spec +func nvidiaHookCount(hooks *specs.Hooks) int { + if hooks == nil { + return 0 + } + + count := 0 + for _, hook := range hooks.Prestart { + if strings.Contains(hook.Path, nvidiaHook) { + count++ + } + } + return count +} + +func TestGetConfigWithCustomConfig(t *testing.T) { + wd, err := os.Getwd() + require.NoError(t, err) + + // By default debug is disabled + contents := []byte("[nvidia-container-runtime]\ndebug = \"/nvidia-container-toolkit.log\"") + testDir := filepath.Join(wd, "test") + filename := filepath.Join(testDir, configFilePath) + + os.Setenv(configOverride, testDir) + + require.NoError(t, os.MkdirAll(filepath.Dir(filename), 0766)) + require.NoError(t, ioutil.WriteFile(filename, contents, 0766)) + + defer func() { require.NoError(t, os.RemoveAll(testDir)) }() + + cfg, err := getConfig() + require.NoError(t, err) + require.Equal(t, cfg.debugFilePath, "/nvidia-container-toolkit.log") +} diff --git a/cmd/nvidia-container-runtime/nvcr.go b/cmd/nvidia-container-runtime/nvcr.go new file mode 100644 index 00000000..1eb23a26 --- /dev/null +++ b/cmd/nvidia-container-runtime/nvcr.go @@ -0,0 +1,145 @@ +/* +# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +*/ + +package main + +import ( + "fmt" + "os" + "os/exec" + "strings" + + "github.com/NVIDIA/nvidia-container-toolkit/internal/oci" + "github.com/opencontainers/runtime-spec/specs-go" + log "github.com/sirupsen/logrus" +) + +// nvidiaContainerRuntime encapsulates the NVIDIA Container Runtime. It wraps the specified runtime, conditionally +// modifying the specified OCI specification before invoking the runtime. +type nvidiaContainerRuntime struct { + logger *log.Logger + runtime oci.Runtime + ociSpec oci.Spec +} + +var _ oci.Runtime = (*nvidiaContainerRuntime)(nil) + +// newNvidiaContainerRuntime is a constructor for a standard runtime shim. +func newNvidiaContainerRuntimeWithLogger(logger *log.Logger, runtime oci.Runtime, ociSpec oci.Spec) (oci.Runtime, error) { + r := nvidiaContainerRuntime{ + logger: logger, + runtime: runtime, + ociSpec: ociSpec, + } + + return &r, nil +} + +// Exec defines the entrypoint for the NVIDIA Container Runtime. A check is performed to see whether modifications +// to the OCI spec are required -- and applicable modifcations applied. The supplied arguments are then +// forwarded to the underlying runtime's Exec method. +func (r nvidiaContainerRuntime) Exec(args []string) error { + if r.modificationRequired(args) { + err := r.modifyOCISpec() + if err != nil { + return fmt.Errorf("error modifying OCI spec: %v", err) + } + } + + r.logger.Println("Forwarding command to runtime") + return r.runtime.Exec(args) +} + +// modificationRequired checks the intput arguments to determine whether a modification +// to the OCI spec is required. +func (r nvidiaContainerRuntime) modificationRequired(args []string) bool { + var previousWasBundle bool + for _, a := range args { + // We check for '--bundle create' explicitly to ensure that we + // don't inadvertently trigger a modification if the bundle directory + // is specified as `create` + if !previousWasBundle && isBundleFlag(a) { + previousWasBundle = true + continue + } + + if !previousWasBundle && a == "create" { + r.logger.Infof("'create' command detected; modification required") + return true + } + + previousWasBundle = false + } + + r.logger.Infof("No modification required") + return false +} + +// modifyOCISpec loads and modifies the OCI spec specified in the nvidiaContainerRuntime +// struct. The spec is modified in-place and written to the same file as the input after +// modifcationas are applied. +func (r nvidiaContainerRuntime) modifyOCISpec() error { + err := r.ociSpec.Load() + if err != nil { + return fmt.Errorf("error loading OCI specification for modification: %v", err) + } + + err = r.ociSpec.Modify(r.addNVIDIAHook) + if err != nil { + return fmt.Errorf("error injecting NVIDIA Container Runtime hook: %v", err) + } + + err = r.ociSpec.Flush() + if err != nil { + return fmt.Errorf("error writing modified OCI specification: %v", err) + } + return nil +} + +// addNVIDIAHook modifies the specified OCI specification in-place, inserting a +// prestart hook. +func (r nvidiaContainerRuntime) addNVIDIAHook(spec *specs.Spec) error { + path, err := exec.LookPath("nvidia-container-runtime-hook") + if err != nil { + path = hookDefaultFilePath + _, err = os.Stat(path) + if err != nil { + return err + } + } + + r.logger.Printf("prestart hook path: %s\n", path) + + args := []string{path} + if spec.Hooks == nil { + spec.Hooks = &specs.Hooks{} + } else if len(spec.Hooks.Prestart) != 0 { + for _, hook := range spec.Hooks.Prestart { + if !strings.Contains(hook.Path, "nvidia-container-runtime-hook") { + continue + } + r.logger.Println("existing nvidia prestart hook in OCI spec file") + return nil + } + } + + spec.Hooks.Prestart = append(spec.Hooks.Prestart, specs.Hook{ + Path: path, + Args: append(args, "prestart"), + }) + + return nil +} diff --git a/cmd/nvidia-container-runtime/nvcr_test.go b/cmd/nvidia-container-runtime/nvcr_test.go new file mode 100644 index 00000000..65cfa524 --- /dev/null +++ b/cmd/nvidia-container-runtime/nvcr_test.go @@ -0,0 +1,230 @@ +/* +# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +*/ + +package main + +import ( + "fmt" + "os" + "strings" + "testing" + + "github.com/NVIDIA/nvidia-container-toolkit/internal/oci" + "github.com/opencontainers/runtime-spec/specs-go" + testlog "github.com/sirupsen/logrus/hooks/test" + "github.com/stretchr/testify/require" +) + +func TestArgsGetConfigFilePath(t *testing.T) { + wd, err := os.Getwd() + require.NoError(t, err) + + testCases := []struct { + bundleDir string + ociSpecPath string + }{ + { + ociSpecPath: fmt.Sprintf("%v/config.json", wd), + }, + { + bundleDir: "/foo/bar", + ociSpecPath: "/foo/bar/config.json", + }, + { + bundleDir: "/foo/bar/", + ociSpecPath: "/foo/bar/config.json", + }, + } + + for i, tc := range testCases { + cp, err := getOCISpecFilePath(tc.bundleDir) + + require.NoErrorf(t, err, "%d: %v", i, tc) + require.Equalf(t, tc.ociSpecPath, cp, "%d: %v", i, tc) + } +} + +func TestAddNvidiaHook(t *testing.T) { + logger, logHook := testlog.NewNullLogger() + shim := nvidiaContainerRuntime{ + logger: logger, + } + + testCases := []struct { + spec *specs.Spec + errorPrefix string + shouldNotAdd bool + }{ + { + spec: &specs.Spec{}, + }, + { + spec: &specs.Spec{ + Hooks: &specs.Hooks{}, + }, + }, + { + spec: &specs.Spec{ + Hooks: &specs.Hooks{ + Prestart: []specs.Hook{{ + Path: "some-hook", + }}, + }, + }, + }, + { + spec: &specs.Spec{ + Hooks: &specs.Hooks{ + Prestart: []specs.Hook{{ + Path: "nvidia-container-runtime-hook", + }}, + }, + }, + shouldNotAdd: true, + }, + } + + for i, tc := range testCases { + logHook.Reset() + + var numPrestartHooks int + if tc.spec.Hooks != nil { + numPrestartHooks = len(tc.spec.Hooks.Prestart) + } + + err := shim.addNVIDIAHook(tc.spec) + + if tc.errorPrefix == "" { + require.NoErrorf(t, err, "%d: %v", i, tc) + } else { + require.Truef(t, strings.HasPrefix(err.Error(), tc.errorPrefix), "%d: %v", i, tc) + + require.NotNilf(t, tc.spec.Hooks, "%d: %v", i, tc) + require.Equalf(t, 1, nvidiaHookCount(tc.spec.Hooks), "%d: %v", i, tc) + + if tc.shouldNotAdd { + require.Equal(t, numPrestartHooks+1, len(tc.spec.Hooks.Poststart), "%d: %v", i, tc) + } else { + require.Equal(t, numPrestartHooks+1, len(tc.spec.Hooks.Poststart), "%d: %v", i, tc) + + nvidiaHook := tc.spec.Hooks.Poststart[len(tc.spec.Hooks.Poststart)-1] + + // TODO: This assumes that the hook has been set up in the makefile + expectedPath := "/usr/bin/nvidia-container-runtime-hook" + require.Equalf(t, expectedPath, nvidiaHook.Path, "%d: %v", i, tc) + require.Equalf(t, []string{expectedPath, "prestart"}, nvidiaHook.Args, "%d: %v", i, tc) + require.Emptyf(t, nvidiaHook.Env, "%d: %v", i, tc) + require.Nilf(t, nvidiaHook.Timeout, "%d: %v", i, tc) + } + } + } +} + +func TestNvidiaContainerRuntime(t *testing.T) { + logger, hook := testlog.NewNullLogger() + + testCases := []struct { + shim nvidiaContainerRuntime + shouldModify bool + args []string + modifyError error + writeError error + }{ + { + shim: nvidiaContainerRuntime{}, + shouldModify: false, + }, + { + shim: nvidiaContainerRuntime{}, + args: []string{"create"}, + shouldModify: true, + }, + { + shim: nvidiaContainerRuntime{}, + args: []string{"--bundle=create"}, + shouldModify: false, + }, + { + shim: nvidiaContainerRuntime{}, + args: []string{"--bundle", "create"}, + shouldModify: false, + }, + { + shim: nvidiaContainerRuntime{}, + args: []string{"create"}, + shouldModify: true, + }, + { + shim: nvidiaContainerRuntime{}, + args: []string{"create"}, + modifyError: fmt.Errorf("error modifying"), + shouldModify: true, + }, + { + shim: nvidiaContainerRuntime{}, + args: []string{"create"}, + writeError: fmt.Errorf("error writing"), + shouldModify: true, + }, + } + + for i, tc := range testCases { + tc.shim.logger = logger + hook.Reset() + + spec := &specs.Spec{} + ociMock := oci.NewMockSpec(spec, tc.writeError, tc.modifyError) + + require.Equal(t, tc.shouldModify, tc.shim.modificationRequired(tc.args), "%d: %v", i, tc) + + tc.shim.ociSpec = ociMock + tc.shim.runtime = &MockShim{} + + err := tc.shim.Exec(tc.args) + if tc.modifyError != nil || tc.writeError != nil { + require.Error(t, err, "%d: %v", i, tc) + } else { + require.NoError(t, err, "%d: %v", i, tc) + } + + if tc.shouldModify { + require.Equal(t, 1, ociMock.MockModify.Callcount, "%d: %v", i, tc) + require.Equal(t, 1, nvidiaHookCount(spec.Hooks), "%d: %v", i, tc) + } else { + require.Equal(t, 0, ociMock.MockModify.Callcount, "%d: %v", i, tc) + require.Nil(t, spec.Hooks, "%d: %v", i, tc) + } + + writeExpected := tc.shouldModify && tc.modifyError == nil + if writeExpected { + require.Equal(t, 1, ociMock.MockFlush.Callcount, "%d: %v", i, tc) + } else { + require.Equal(t, 0, ociMock.MockFlush.Callcount, "%d: %v", i, tc) + } + } +} + +type MockShim struct { + called bool + args []string + returnError error +} + +func (m *MockShim) Exec(args []string) error { + m.called = true + m.args = args + return m.returnError +} diff --git a/cmd/nvidia-container-runtime/runtime_factory.go b/cmd/nvidia-container-runtime/runtime_factory.go new file mode 100644 index 00000000..79db2415 --- /dev/null +++ b/cmd/nvidia-container-runtime/runtime_factory.go @@ -0,0 +1,176 @@ +/* +# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +*/ + +package main + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/NVIDIA/nvidia-container-toolkit/internal/oci" +) + +const ( + ociSpecFileName = "config.json" + dockerRuncExecutableName = "docker-runc" + runcExecutableName = "runc" +) + +// newRuntime is a factory method that constructs a runtime based on the selected configuration. +func newRuntime(argv []string) (oci.Runtime, error) { + ociSpec, err := newOCISpec(argv) + if err != nil { + return nil, fmt.Errorf("error constructing OCI specification: %v", err) + } + + runc, err := newRuncRuntime() + if err != nil { + return nil, fmt.Errorf("error constructing runc runtime: %v", err) + } + + r, err := newNvidiaContainerRuntimeWithLogger(logger.Logger, runc, ociSpec) + if err != nil { + return nil, fmt.Errorf("error constructing NVIDIA Container Runtime: %v", err) + } + + return r, nil +} + +// newOCISpec constructs an OCI spec for the provided arguments +func newOCISpec(argv []string) (oci.Spec, error) { + bundlePath, err := getBundlePath(argv) + if err != nil { + return nil, fmt.Errorf("error parsing command line arguments: %v", err) + } + + ociSpecPath, err := getOCISpecFilePath(bundlePath) + if err != nil { + return nil, fmt.Errorf("error getting OCI specification file path: %v", err) + } + ociSpec := oci.NewSpecFromFile(ociSpecPath) + + return ociSpec, nil +} + +// newRuncRuntime locates the runc binary and wraps it in a SyscallExecRuntime +func newRuncRuntime() (oci.Runtime, error) { + runtimePath, err := findRunc() + if err != nil { + return nil, fmt.Errorf("error locating runtime: %v", err) + } + + runc, err := oci.NewSyscallExecRuntimeWithLogger(logger.Logger, runtimePath) + if err != nil { + return nil, fmt.Errorf("error constructing runtime: %v", err) + } + + return runc, nil +} + +// getBundlePath checks the specified slice of strings (argv) for a 'bundle' flag as allowed by runc. +// The following are supported: +// --bundle{{SEP}}BUNDLE_PATH +// -bundle{{SEP}}BUNDLE_PATH +// -b{{SEP}}BUNDLE_PATH +// where {{SEP}} is either ' ' or '=' +func getBundlePath(argv []string) (string, error) { + var bundlePath string + + for i := 0; i < len(argv); i++ { + param := argv[i] + + parts := strings.SplitN(param, "=", 2) + if !isBundleFlag(parts[0]) { + continue + } + + // The flag has the format --bundle=/path + if len(parts) == 2 { + bundlePath = parts[1] + continue + } + + // The flag has the format --bundle /path + if i+1 < len(argv) { + bundlePath = argv[i+1] + i++ + continue + } + + // --bundle / -b was the last element of argv + return "", fmt.Errorf("bundle option requires an argument") + } + + return bundlePath, nil +} + +// findRunc locates runc in the path, returning the full path to the +// binary or an error. +func findRunc() (string, error) { + runtimeCandidates := []string{ + dockerRuncExecutableName, + runcExecutableName, + } + + return findRuntime(runtimeCandidates) +} + +func findRuntime(runtimeCandidates []string) (string, error) { + for _, candidate := range runtimeCandidates { + logger.Infof("Looking for runtime binary '%v'", candidate) + runcPath, err := exec.LookPath(candidate) + if err == nil { + logger.Infof("Found runtime binary '%v'", runcPath) + return runcPath, nil + } + logger.Warnf("Runtime binary '%v' not found: %v", candidate, err) + } + + return "", fmt.Errorf("no runtime binary found from candidate list: %v", runtimeCandidates) +} + +func isBundleFlag(arg string) bool { + if !strings.HasPrefix(arg, "-") { + return false + } + + trimmed := strings.TrimLeft(arg, "-") + return trimmed == "b" || trimmed == "bundle" +} + +// getOCISpecFilePath returns the expected path to the OCI specification file for the given +// bundle directory or the current working directory if not specified. +func getOCISpecFilePath(bundleDir string) (string, error) { + if bundleDir == "" { + logger.Infof("Bundle directory path is empty, using working directory.") + workingDirectory, err := os.Getwd() + if err != nil { + return "", fmt.Errorf("error getting working directory: %v", err) + } + bundleDir = workingDirectory + } + + logger.Infof("Using bundle directory: %v", bundleDir) + + OCISpecFilePath := filepath.Join(bundleDir, ociSpecFileName) + + logger.Infof("Using OCI specification file path: %v", OCISpecFilePath) + + return OCISpecFilePath, nil +} diff --git a/cmd/nvidia-container-runtime/runtime_factory_test.go b/cmd/nvidia-container-runtime/runtime_factory_test.go new file mode 100644 index 00000000..b5a6d461 --- /dev/null +++ b/cmd/nvidia-container-runtime/runtime_factory_test.go @@ -0,0 +1,192 @@ +/* +# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +*/ + +package main + +import ( + "path/filepath" + "testing" + + testlog "github.com/sirupsen/logrus/hooks/test" + "github.com/stretchr/testify/require" +) + +func TestConstructor(t *testing.T) { + shim, err := newRuntime([]string{}) + + require.NoError(t, err) + require.NotNil(t, shim) +} + +func TestGetBundlePath(t *testing.T) { + type expected struct { + bundle string + isError bool + } + testCases := []struct { + argv []string + expected expected + }{ + { + argv: []string{}, + }, + { + argv: []string{"create"}, + }, + { + argv: []string{"--bundle"}, + expected: expected{ + isError: true, + }, + }, + { + argv: []string{"-b"}, + expected: expected{ + isError: true, + }, + }, + { + argv: []string{"--bundle", "/foo/bar"}, + expected: expected{ + bundle: "/foo/bar", + }, + }, + { + argv: []string{"--not-bundle", "/foo/bar"}, + }, + { + argv: []string{"--"}, + }, + { + argv: []string{"-bundle", "/foo/bar"}, + expected: expected{ + bundle: "/foo/bar", + }, + }, + { + argv: []string{"--bundle=/foo/bar"}, + expected: expected{ + bundle: "/foo/bar", + }, + }, + { + argv: []string{"-b=/foo/bar"}, + expected: expected{ + bundle: "/foo/bar", + }, + }, + { + argv: []string{"-b=/foo/=bar"}, + expected: expected{ + bundle: "/foo/=bar", + }, + }, + { + argv: []string{"-b", "/foo/bar"}, + expected: expected{ + bundle: "/foo/bar", + }, + }, + { + argv: []string{"create", "-b", "/foo/bar"}, + expected: expected{ + bundle: "/foo/bar", + }, + }, + { + argv: []string{"-b", "create", "create"}, + expected: expected{ + bundle: "create", + }, + }, + { + argv: []string{"-b=create", "create"}, + expected: expected{ + bundle: "create", + }, + }, + { + argv: []string{"-b", "create"}, + expected: expected{ + bundle: "create", + }, + }, + } + + for i, tc := range testCases { + bundle, err := getBundlePath(tc.argv) + + if tc.expected.isError { + require.Errorf(t, err, "%d: %v", i, tc) + } else { + require.NoErrorf(t, err, "%d: %v", i, tc) + } + + require.Equalf(t, tc.expected.bundle, bundle, "%d: %v", i, tc) + } +} + +func TestFindRunc(t *testing.T) { + testLogger, _ := testlog.NewNullLogger() + logger.Logger = testLogger + + runcPath, err := findRunc() + require.NoError(t, err) + require.Equal(t, filepath.Join(cfg.binPath, runcExecutableName), runcPath) +} + +func TestFindRuntime(t *testing.T) { + testLogger, _ := testlog.NewNullLogger() + logger.Logger = testLogger + + testCases := []struct { + candidates []string + expectedPath string + }{ + { + candidates: []string{}, + }, + { + candidates: []string{"not-runc"}, + }, + { + candidates: []string{"not-runc", "also-not-runc"}, + }, + { + candidates: []string{runcExecutableName}, + expectedPath: filepath.Join(cfg.binPath, runcExecutableName), + }, + { + candidates: []string{runcExecutableName, "not-runc"}, + expectedPath: filepath.Join(cfg.binPath, runcExecutableName), + }, + { + candidates: []string{"not-runc", runcExecutableName}, + expectedPath: filepath.Join(cfg.binPath, runcExecutableName), + }, + } + + for i, tc := range testCases { + runcPath, err := findRuntime(tc.candidates) + if tc.expectedPath == "" { + require.Error(t, err, "%d: %v", i, tc) + } else { + require.NoError(t, err, "%d: %v", i, tc) + } + require.Equal(t, tc.expectedPath, runcPath, "%d: %v", i, tc) + } + +} diff --git a/docker/Dockerfile.amazonlinux b/docker/Dockerfile.amazonlinux index 46af3780..12a6732f 100644 --- a/docker/Dockerfile.amazonlinux +++ b/docker/Dockerfile.amazonlinux @@ -40,8 +40,7 @@ RUN mkdir -p $DIST_DIR /dist WORKDIR $GOPATH/src/nvidia-container-toolkit COPY . . -RUN make binary && \ - mv ./nvidia-container-toolkit $DIST_DIR/nvidia-container-toolkit +RUN make PREFIX=${DIST_DIR} cmds COPY config/config.toml.amzn $DIST_DIR/config.toml diff --git a/docker/Dockerfile.centos b/docker/Dockerfile.centos index ee1ec486..94e46e4c 100644 --- a/docker/Dockerfile.centos +++ b/docker/Dockerfile.centos @@ -40,8 +40,7 @@ RUN mkdir -p $DIST_DIR /dist WORKDIR $GOPATH/src/nvidia-container-toolkit COPY . . -RUN make binary && \ - mv ./nvidia-container-toolkit $DIST_DIR/nvidia-container-toolkit +RUN make PREFIX=${DIST_DIR} cmds COPY config/config.toml.centos $DIST_DIR/config.toml diff --git a/docker/Dockerfile.debian b/docker/Dockerfile.debian index 8329dfb9..bafa1dbb 100644 --- a/docker/Dockerfile.debian +++ b/docker/Dockerfile.debian @@ -48,8 +48,7 @@ RUN mkdir -p $DIST_DIR /dist WORKDIR $GOPATH/src/nvidia-container-toolkit COPY . . -RUN make binary && \ - mv ./nvidia-container-toolkit $DIST_DIR/nvidia-container-toolkit +RUN make PREFIX=${DIST_DIR} cmds COPY config/config.toml.debian $DIST_DIR/config.toml diff --git a/docker/Dockerfile.opensuse-leap b/docker/Dockerfile.opensuse-leap index c9ac070a..6fbdab15 100644 --- a/docker/Dockerfile.opensuse-leap +++ b/docker/Dockerfile.opensuse-leap @@ -39,8 +39,7 @@ RUN mkdir -p $DIST_DIR /dist WORKDIR $GOPATH/src/nvidia-container-toolkit COPY . . -RUN make binary && \ - mv ./nvidia-container-toolkit $DIST_DIR/nvidia-container-toolkit +RUN make PREFIX=${DIST_DIR} cmds # Hook for Project Atomic's fork of Docker: https://github.com/projectatomic/docker/tree/docker-1.13.1-rhel#add-dockerhooks-exec-custom-hooks-for-prestartpoststop-containerspatch COPY oci-nvidia-hook $DIST_DIR/oci-nvidia-hook diff --git a/docker/Dockerfile.ubuntu b/docker/Dockerfile.ubuntu index 649cdf3a..7f2571af 100644 --- a/docker/Dockerfile.ubuntu +++ b/docker/Dockerfile.ubuntu @@ -46,8 +46,7 @@ RUN mkdir -p $DIST_DIR /dist WORKDIR $GOPATH/src/nvidia-container-toolkit COPY . . -RUN make binary && \ - mv ./nvidia-container-toolkit $DIST_DIR/nvidia-container-toolkit +RUN make PREFIX=${DIST_DIR} cmds COPY config/config.toml.ubuntu $DIST_DIR/config.toml diff --git a/go.mod b/go.mod index 5ba2c48c..b692664b 100644 --- a/go.mod +++ b/go.mod @@ -9,5 +9,6 @@ require ( github.com/pelletier/go-toml v1.9.3 github.com/sirupsen/logrus v1.8.1 github.com/stretchr/testify v1.7.0 + github.com/tsaikd/KDGoLib v0.0.0-20191001134900-7f3cf518e07d golang.org/x/mod v0.3.0 ) diff --git a/go.sum b/go.sum index 81398e93..420db62f 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,10 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/NVIDIA/go-nvml v0.11.1-0/go.mod h1:hy7HYeQy335x6nEss0Ne3PYqleRa6Ct+VKD9RQ4nyFs= @@ -10,22 +17,244 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11/go.mod h1:Ah2dBMoxZEqk118as2T4u4fjfXarE0pPnMJaArZQZsI= +github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nlopes/slack v0.6.0/go.mod h1:JzQ9m3PMAqcpeCam7UaHSuBuupz7CmpjehYMayT6YOk= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tsaikd/KDGoLib v0.0.0-20191001134900-7f3cf518e07d/go.mod h1:oFPCwcQpP90RVZxlBdgPN+iu2tPkboPUa4xaVEI6pO4= +github.com/tsaikd/govalidator v0.0.0-20161031084447-986f2244fc69/go.mod h1:yJymgtZhuWi1Ih5t37Ej381BGZFZvlb9YMTwBxB/QjU= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191001123449-8b695b21ef34/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.10.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/internal/oci/runtime.go b/internal/oci/runtime.go new file mode 100644 index 00000000..89df5aa1 --- /dev/null +++ b/internal/oci/runtime.go @@ -0,0 +1,23 @@ +/* +# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +*/ + +package oci + +// Runtime is an interface for a runtime shim. The Exec method accepts a list +// of command line arguments, and returns an error / nil. +type Runtime interface { + Exec([]string) error +} diff --git a/internal/oci/runtime_exec.go b/internal/oci/runtime_exec.go new file mode 100644 index 00000000..98415747 --- /dev/null +++ b/internal/oci/runtime_exec.go @@ -0,0 +1,79 @@ +/* +# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +*/ + +package oci + +import ( + "fmt" + "os" + "syscall" + + log "github.com/sirupsen/logrus" +) + +// SyscallExecRuntime wraps the path that a binary and defines the semanitcs for how to exec into it. +// This can be used to wrap an OCI-compliant low-level runtime binary, allowing it to be used through the +// Runtime internface. +type SyscallExecRuntime struct { + logger *log.Logger + path string + // exec is used for testing. This defaults to syscall.Exec + exec func(argv0 string, argv []string, envv []string) error +} + +var _ Runtime = (*SyscallExecRuntime)(nil) + +// NewSyscallExecRuntime creates a SyscallExecRuntime for the specified path with the standard logger +func NewSyscallExecRuntime(path string) (Runtime, error) { + return NewSyscallExecRuntimeWithLogger(log.StandardLogger(), path) +} + +// NewSyscallExecRuntimeWithLogger creates a SyscallExecRuntime for the specified logger and path +func NewSyscallExecRuntimeWithLogger(logger *log.Logger, path string) (Runtime, error) { + info, err := os.Stat(path) + if err != nil { + return nil, fmt.Errorf("invalid path '%v': %v", path, err) + } + if info.IsDir() || info.Mode()&0111 == 0 { + return nil, fmt.Errorf("specified path '%v' is not an executable file", path) + } + + shim := SyscallExecRuntime{ + logger: logger, + path: path, + exec: syscall.Exec, + } + + return &shim, nil +} + +// Exec exces into the binary at the path from the SyscallExecRuntime struct, passing it the supplied arguments +// after ensuring that the first argument is the path of the target binary. +func (s SyscallExecRuntime) Exec(args []string) error { + runtimeArgs := []string{s.path} + if len(args) > 1 { + runtimeArgs = append(runtimeArgs, args[1:]...) + } + + err := s.exec(s.path, runtimeArgs, os.Environ()) + if err != nil { + return fmt.Errorf("could not exec '%v': %v", s.path, err) + } + + // syscall.Exec is not expected to return. This is an error state regardless of whether + // err is nil or not. + return fmt.Errorf("unexpected return from exec '%v'", s.path) +} diff --git a/internal/oci/runtime_exec_test.go b/internal/oci/runtime_exec_test.go new file mode 100644 index 00000000..83ac64a2 --- /dev/null +++ b/internal/oci/runtime_exec_test.go @@ -0,0 +1,100 @@ +/* +# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +*/ +package oci + +import ( + "fmt" + "strings" + "testing" + + testlog "github.com/sirupsen/logrus/hooks/test" + "github.com/stretchr/testify/require" +) + +func TestSyscallExecConstructor(t *testing.T) { + r, err := NewSyscallExecRuntime("////an/invalid/path") + require.Error(t, err) + require.Nil(t, r) + + r, err = NewSyscallExecRuntime("/tmp") + require.Error(t, err) + require.Nil(t, r) + + r, err = NewSyscallExecRuntime("/dev/null") + require.Error(t, err) + require.Nil(t, r) + + r, err = NewSyscallExecRuntime("/bin/sh") + require.NoError(t, err) + + f, ok := r.(*SyscallExecRuntime) + require.True(t, ok) + + require.Equal(t, "/bin/sh", f.path) +} + +func TestSyscallExecForwardsArgs(t *testing.T) { + logger, _ := testlog.NewNullLogger() + f := SyscallExecRuntime{ + logger: logger, + path: "runtime", + } + + testCases := []struct { + returnError error + args []string + errorPrefix string + }{ + { + returnError: nil, + errorPrefix: "unexpected return from exec", + }, + { + returnError: fmt.Errorf("error from exec"), + errorPrefix: "could not exec", + }, + { + returnError: nil, + args: []string{"otherargv0"}, + errorPrefix: "unexpected return from exec", + }, + { + returnError: nil, + args: []string{"otherargv0", "arg1", "arg2", "arg3"}, + errorPrefix: "unexpected return from exec", + }, + } + + for i, tc := range testCases { + execMock := WithMockExec(f, tc.returnError) + + err := execMock.Exec(tc.args) + + require.Errorf(t, err, "%d: %v", i, tc) + require.Truef(t, strings.HasPrefix(err.Error(), tc.errorPrefix), "%d: %v", i, tc) + if tc.returnError != nil { + require.Truef(t, strings.HasSuffix(err.Error(), tc.returnError.Error()), "%d: %v", i, tc) + } + + require.Equalf(t, f.path, execMock.argv0, "%d: %v", i, tc) + require.Equalf(t, f.path, execMock.argv[0], "%d: %v", i, tc) + + require.LessOrEqualf(t, len(tc.args), len(execMock.argv), "%d: %v", i, tc) + if len(tc.args) > 1 { + require.Equalf(t, tc.args[1:], execMock.argv[1:], "%d: %v", i, tc) + } + } +} diff --git a/internal/oci/runtime_mock.go b/internal/oci/runtime_mock.go new file mode 100644 index 00000000..e09cfb79 --- /dev/null +++ b/internal/oci/runtime_mock.go @@ -0,0 +1,49 @@ +/* +# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +*/ + +package oci + +// MockExecRuntime wraps a SyscallExecRuntime, intercepting the exec call for testing +type MockExecRuntime struct { + SyscallExecRuntime + execMock +} + +// WithMockExec wraps a specified SyscallExecRuntime with a mocked exec function for testing +func WithMockExec(e SyscallExecRuntime, execResult error) *MockExecRuntime { + m := MockExecRuntime{ + SyscallExecRuntime: e, + execMock: execMock{result: execResult}, + } + // overrdie the exec function to the mocked exec function. + m.SyscallExecRuntime.exec = m.execMock.exec + return &m +} + +type execMock struct { + argv0 string + argv []string + envv []string + result error +} + +func (m *execMock) exec(argv0 string, argv []string, envv []string) error { + m.argv0 = argv0 + m.argv = argv + m.envv = envv + + return m.result +} diff --git a/internal/oci/spec.go b/internal/oci/spec.go new file mode 100644 index 00000000..0c3aaf4d --- /dev/null +++ b/internal/oci/spec.go @@ -0,0 +1,102 @@ +/* +# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +*/ + +package oci + +import ( + "encoding/json" + "fmt" + "os" + + oci "github.com/opencontainers/runtime-spec/specs-go" +) + +// SpecModifier is a function that accepts a pointer to an OCI Srec and returns an +// error. The intention is that the function would modify the spec in-place. +type SpecModifier func(*oci.Spec) error + +// Spec defines the operations to be performed on an OCI specification +type Spec interface { + Load() error + Flush() error + Modify(SpecModifier) error +} + +type fileSpec struct { + *oci.Spec + path string +} + +var _ Spec = (*fileSpec)(nil) + +// NewSpecFromFile creates an object that encapsulates a file-backed OCI spec. +// This can be used to read from the file, modify the spec, and write to the +// same file. +func NewSpecFromFile(filepath string) Spec { + oci := fileSpec{ + path: filepath, + } + + return &oci +} + +// Load reads the contents of an OCI spec from file to be referenced internally. +// The file is opened "read-only" +func (s *fileSpec) Load() error { + specFile, err := os.Open(s.path) + if err != nil { + return fmt.Errorf("error opening OCI specification file: %v", err) + } + defer specFile.Close() + + decoder := json.NewDecoder(specFile) + + var spec oci.Spec + err = decoder.Decode(&spec) + if err != nil { + return fmt.Errorf("error reading OCI specification from file: %v", err) + } + + s.Spec = &spec + return nil +} + +// Modify applies the specified SpecModifier to the stored OCI specification. +func (s *fileSpec) Modify(f SpecModifier) error { + if s.Spec == nil { + return fmt.Errorf("no spec loaded for modification") + } + return f(s.Spec) +} + +// Flush writes the stored OCI specification to the filepath specifed by the path member. +// The file is truncated upon opening, overwriting any existing contents. +func (s fileSpec) Flush() error { + specFile, err := os.Create(s.path) + if err != nil { + return fmt.Errorf("error opening OCI specification file: %v", err) + } + defer specFile.Close() + + encoder := json.NewEncoder(specFile) + + err = encoder.Encode(s.Spec) + if err != nil { + return fmt.Errorf("error writing OCI specification to file: %v", err) + } + + return nil +} diff --git a/internal/oci/spec_mock.go b/internal/oci/spec_mock.go new file mode 100644 index 00000000..1247adaf --- /dev/null +++ b/internal/oci/spec_mock.go @@ -0,0 +1,70 @@ +/* +# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +*/ + +package oci + +import ( + oci "github.com/opencontainers/runtime-spec/specs-go" +) + +// MockSpec provides a simple mock for an OCI spec to be used in testing. +// It also implements the SpecModifier interface. +type MockSpec struct { + *oci.Spec + MockLoad mockFunc + MockFlush mockFunc + MockModify mockFunc +} + +var _ Spec = (*MockSpec)(nil) + +// NewMockSpec constructs a MockSpec to be used in testing as a Spec +func NewMockSpec(spec *oci.Spec, flushResult error, modifyResult error) *MockSpec { + s := MockSpec{ + Spec: spec, + MockFlush: mockFunc{result: flushResult}, + MockModify: mockFunc{result: modifyResult}, + } + + return &s +} + +// Load invokes the mocked Load function to return the predefined error / result +func (s *MockSpec) Load() error { + return s.MockLoad.call() +} + +// Flush invokes the mocked Load function to return the predefined error / result +func (s *MockSpec) Flush() error { + return s.MockFlush.call() +} + +// Modify applies the specified SpecModifier to the spec and invokes the +// mocked modify function to return the predefined error / result. +func (s *MockSpec) Modify(f SpecModifier) error { + f(s.Spec) + return s.MockModify.call() +} + +type mockFunc struct { + Callcount int + result error +} + +func (m *mockFunc) call() error { + m.Callcount++ + return m.result +} diff --git a/packaging/debian/changelog b/packaging/debian/changelog index 12b90f99..da13bd12 100644 --- a/packaging/debian/changelog +++ b/packaging/debian/changelog @@ -1,3 +1,9 @@ +nvidia-container-toolkit (1.5.2~rc.1-1) experimental; urgency=medium + + * Include nvidia-container-runtime into nvidia-container-toolkit package + + -- NVIDIA CORPORATION Mon, 06 Sep 2021 12:24:05 +0200 + nvidia-container-toolkit (1.5.1-1) UNRELEASED; urgency=medium * Fix bug where Docker Swarm device selection is ignored if diff --git a/packaging/debian/control b/packaging/debian/control index 7dbec179..e02e0931 100644 --- a/packaging/debian/control +++ b/packaging/debian/control @@ -10,8 +10,8 @@ Build-Depends: debhelper (>= 9) Package: nvidia-container-toolkit Architecture: any -Depends: ${misc:Depends}, libnvidia-container-tools (>= 1.4.0), libnvidia-container-tools (<< 2.0.0) -Breaks: nvidia-container-runtime (<< 2.0.0), nvidia-container-runtime-hook -Replaces: nvidia-container-runtime (<< 2.0.0), nvidia-container-runtime-hook +Depends: ${misc:Depends}, libnvidia-container-tools (>= 1.4.0), libnvidia-container-tools (<< 2.0.0), libseccomp2 +Breaks: nvidia-container-runtime (<= 3.5.0), nvidia-container-runtime-hook +Replaces: nvidia-container-runtime (<= 3.5.0), nvidia-container-runtime-hook Description: NVIDIA container runtime hook Provides a OCI hook to enable GPU support in containers. diff --git a/packaging/debian/nvidia-container-toolkit.install b/packaging/debian/nvidia-container-toolkit.install index 6975c4bd..9547c0c9 100644 --- a/packaging/debian/nvidia-container-toolkit.install +++ b/packaging/debian/nvidia-container-toolkit.install @@ -1,2 +1,3 @@ config.toml /etc/nvidia-container-runtime nvidia-container-toolkit /usr/bin +nvidia-container-runtime /usr/bin \ No newline at end of file diff --git a/packaging/rpm/SPECS/nvidia-container-toolkit.spec b/packaging/rpm/SPECS/nvidia-container-toolkit.spec index 09a6591c..3a1ad3f0 100644 --- a/packaging/rpm/SPECS/nvidia-container-toolkit.spec +++ b/packaging/rpm/SPECS/nvidia-container-toolkit.spec @@ -11,24 +11,34 @@ URL: https://github.com/NVIDIA/nvidia-container-runtime License: Apache-2.0 Source0: nvidia-container-toolkit -Source1: config.toml -Source2: oci-nvidia-hook -Source3: oci-nvidia-hook.json -Source4: LICENSE +Source1: nvidia-container-runtime +Source2: config.toml +Source3: oci-nvidia-hook +Source4: oci-nvidia-hook.json +Source5: LICENSE -Obsoletes: nvidia-container-runtime < 2.0.0, nvidia-container-runtime-hook +Obsoletes: nvidia-container-runtime <= 3.5.0, nvidia-container-runtime-hook +Provides: nvidia-container-runtime Provides: nvidia-container-runtime-hook Requires: libnvidia-container-tools >= 1.4.0, libnvidia-container-tools < 2.0.0 +%if 0%{?suse_version} +Requires: libseccomp2 +Requires: libapparmor1 +%else +Requires: libseccomp +%endif + %description Provides a OCI hook to enable GPU support in containers. %prep -cp %{SOURCE0} %{SOURCE1} %{SOURCE2} %{SOURCE3} %{SOURCE4} . +cp %{SOURCE0} %{SOURCE1} %{SOURCE2} %{SOURCE3} %{SOURCE4} %{SOURCE5} . %install mkdir -p %{buildroot}%{_bindir} install -m 755 -t %{buildroot}%{_bindir} nvidia-container-toolkit +install -m 755 -t %{buildroot}%{_bindir} nvidia-container-runtime mkdir -p %{buildroot}/etc/nvidia-container-runtime install -m 644 -t %{buildroot}/etc/nvidia-container-runtime config.toml @@ -48,11 +58,16 @@ rm -f %{_bindir}/nvidia-container-runtime-hook %files %license LICENSE %{_bindir}/nvidia-container-toolkit +%{_bindir}/nvidia-container-runtime %config /etc/nvidia-container-runtime/config.toml /usr/libexec/oci/hooks.d/oci-nvidia-hook /usr/share/containers/oci/hooks.d/oci-nvidia-hook.json %changelog +* Mon Sep 06 2021 NVIDIA CORPORATION 1.5.2-0.1.rc.1 + +- Include nvidia-container-runtime into nvidia-container-toolkit package + * Mon Jun 14 2021 NVIDIA CORPORATION 1.5.1-1 - Fix bug where Docker Swarm device selection is ignored if NVIDIA_VISIBLE_DEVICES is also set diff --git a/test/bin/nvidia-container-runtime-hook b/test/bin/nvidia-container-runtime-hook new file mode 100755 index 00000000..5579c7d1 --- /dev/null +++ b/test/bin/nvidia-container-runtime-hook @@ -0,0 +1,2 @@ +#!/bin/bash +echo mock hook diff --git a/test/bin/runc b/test/bin/runc new file mode 100755 index 00000000..e605fc8d --- /dev/null +++ b/test/bin/runc @@ -0,0 +1,2 @@ +#!/bin/bash +echo mock runc diff --git a/test/input/nvidia-container-runtime/config.toml b/test/input/nvidia-container-runtime/config.toml new file mode 100644 index 00000000..e69de29b diff --git a/test/input/test_spec.json b/test/input/test_spec.json new file mode 100644 index 00000000..35a1a875 --- /dev/null +++ b/test/input/test_spec.json @@ -0,0 +1,178 @@ +{ + "ociVersion": "1.0.1-dev", + "process": { + "terminal": true, + "user": { + "uid": 0, + "gid": 0 + }, + "args": [ + "sh" + ], + "env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "TERM=xterm" + ], + "cwd": "/", + "capabilities": { + "bounding": [ + "CAP_AUDIT_WRITE", + "CAP_KILL", + "CAP_NET_BIND_SERVICE" + ], + "effective": [ + "CAP_AUDIT_WRITE", + "CAP_KILL", + "CAP_NET_BIND_SERVICE" + ], + "inheritable": [ + "CAP_AUDIT_WRITE", + "CAP_KILL", + "CAP_NET_BIND_SERVICE" + ], + "permitted": [ + "CAP_AUDIT_WRITE", + "CAP_KILL", + "CAP_NET_BIND_SERVICE" + ], + "ambient": [ + "CAP_AUDIT_WRITE", + "CAP_KILL", + "CAP_NET_BIND_SERVICE" + ] + }, + "rlimits": [ + { + "type": "RLIMIT_NOFILE", + "hard": 1024, + "soft": 1024 + } + ], + "noNewPrivileges": true + }, + "root": { + "path": "rootfs", + "readonly": true + }, + "hostname": "runc", + "mounts": [ + { + "destination": "/proc", + "type": "proc", + "source": "proc" + }, + { + "destination": "/dev", + "type": "tmpfs", + "source": "tmpfs", + "options": [ + "nosuid", + "strictatime", + "mode=755", + "size=65536k" + ] + }, + { + "destination": "/dev/pts", + "type": "devpts", + "source": "devpts", + "options": [ + "nosuid", + "noexec", + "newinstance", + "ptmxmode=0666", + "mode=0620", + "gid=5" + ] + }, + { + "destination": "/dev/shm", + "type": "tmpfs", + "source": "shm", + "options": [ + "nosuid", + "noexec", + "nodev", + "mode=1777", + "size=65536k" + ] + }, + { + "destination": "/dev/mqueue", + "type": "mqueue", + "source": "mqueue", + "options": [ + "nosuid", + "noexec", + "nodev" + ] + }, + { + "destination": "/sys", + "type": "sysfs", + "source": "sysfs", + "options": [ + "nosuid", + "noexec", + "nodev", + "ro" + ] + }, + { + "destination": "/sys/fs/cgroup", + "type": "cgroup", + "source": "cgroup", + "options": [ + "nosuid", + "noexec", + "nodev", + "relatime", + "ro" + ] + } + ], + "linux": { + "resources": { + "devices": [ + { + "allow": false, + "access": "rwm" + } + ] + }, + "namespaces": [ + { + "type": "pid" + }, + { + "type": "network" + }, + { + "type": "ipc" + }, + { + "type": "uts" + }, + { + "type": "mount" + } + ], + "maskedPaths": [ + "/proc/kcore", + "/proc/latency_stats", + "/proc/timer_list", + "/proc/timer_stats", + "/proc/sched_debug", + "/sys/firmware", + "/proc/scsi" + ], + "readonlyPaths": [ + "/proc/asound", + "/proc/bus", + "/proc/fs", + "/proc/irq", + "/proc/sys", + "/proc/sysrq-trigger" + ] + } +} \ No newline at end of file diff --git a/vendor/github.com/tsaikd/KDGoLib/LICENSE b/vendor/github.com/tsaikd/KDGoLib/LICENSE new file mode 100644 index 00000000..6600f1c9 --- /dev/null +++ b/vendor/github.com/tsaikd/KDGoLib/LICENSE @@ -0,0 +1,165 @@ +GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/vendor/github.com/tsaikd/KDGoLib/errutil/ConsoleFormatter.go b/vendor/github.com/tsaikd/KDGoLib/errutil/ConsoleFormatter.go new file mode 100644 index 00000000..83ba46f2 --- /dev/null +++ b/vendor/github.com/tsaikd/KDGoLib/errutil/ConsoleFormatter.go @@ -0,0 +1,88 @@ +package errutil + +import ( + "bytes" + "time" +) + +// NewConsoleFormatter create JSONErrorFormatter instance +func NewConsoleFormatter(seperator string) *ConsoleFormatter { + return &ConsoleFormatter{ + Seperator: seperator, + } +} + +// ConsoleFormatter used to format error object in console readable +type ConsoleFormatter struct { + // seperator between errors, e.g. "; " will output "err1; err2; err3" + Seperator string + // output timestamp for prefix, e.g. "2006-01-02 15:04:05 " + TimeFormat string + // show error position with long filename + LongFile bool + // show error position with short filename + ShortFile bool + // show error position with line number, work with LongFile or ShortFile + Line bool + // replace package name for securify + ReplacePackages map[string]string +} + +// Format error object +func (t *ConsoleFormatter) Format(errin error) (errtext string, err error) { + return t.FormatSkip(errin, 1) +} + +// FormatSkip trace error line and format object +func (t *ConsoleFormatter) FormatSkip(errin error, skip int) (errtext string, err error) { + errobj := castErrorObject(nil, skip+1, errin) + if errobj == nil { + return "", nil + } + + buffer := &bytes.Buffer{} + + if t.TimeFormat != "" { + if _, errio := buffer.WriteString(time.Now().Format(t.TimeFormat)); errio != nil { + return buffer.String(), errio + } + } + + if t.LongFile || t.ShortFile { + if _, errio := WriteCallInfo(buffer, errobj, t.LongFile, t.Line, t.ReplacePackages); errio != nil { + return buffer.String(), errio + } + if _, errio := buffer.WriteString(" "); errio != nil { + return buffer.String(), errio + } + } + + if t.Seperator == "" { + if _, errio := buffer.WriteString(getErrorText(errin)); errio != nil { + return buffer.String(), errio + } + return buffer.String(), nil + } + + firstError := true + if walkerr := WalkErrors(errobj, func(errloop ErrorObject) (stop bool, walkerr error) { + if !firstError { + if _, errio := buffer.WriteString(t.Seperator); errio != nil { + return true, errio + } + } + firstError = false + + if _, errio := buffer.WriteString(getErrorText(errloop)); errio != nil { + return true, errio + } + return false, nil + }); walkerr != nil { + return buffer.String(), walkerr + } + + return buffer.String(), nil +} + +var _ ErrorFormatter = &ConsoleFormatter{} +var _ TraceFormatter = &ConsoleFormatter{} diff --git a/vendor/github.com/tsaikd/KDGoLib/errutil/ErrorFactory.go b/vendor/github.com/tsaikd/KDGoLib/errutil/ErrorFactory.go new file mode 100644 index 00000000..5233bc40 --- /dev/null +++ b/vendor/github.com/tsaikd/KDGoLib/errutil/ErrorFactory.go @@ -0,0 +1,113 @@ +package errutil + +import ( + "fmt" + "sort" +) + +// ErrorFactory is used for create or check ErrorObject +type ErrorFactory interface { + Error() string + Name() string + + New(err error, params ...interface{}) ErrorObject + Match(err error) bool + In(err error) bool +} + +type errorFactory struct { + errtext string + name string +} + +var namedFactories = map[string]ErrorFactory{} + +// AllNamedFactories return all named factories +func AllNamedFactories() map[string]ErrorFactory { + return namedFactories +} + +// AllSortedNamedFactories return all sorted named factories +// NOTE: this is slow for sorting in runtime +func AllSortedNamedFactories() []ErrorFactory { + sorter := newSorter(namedFactories) + sort.Sort(sorter) + return sorter.data +} + +// NewFactory return new ErrorFactory instance +func NewFactory(errtext string) ErrorFactory { + callinfo, _ := RuntimeCaller(1) + return NewNamedFactory(callinfo.PackageName()+"->"+errtext, errtext) +} + +// NewNamedFactory return new ErrorFactory instance with factory name, panic if name duplicated +func NewNamedFactory(name string, errtext string) ErrorFactory { + if _, ok := namedFactories[name]; ok { + panic(fmt.Errorf("error factory name duplicated: %q", name)) + } + factory := &errorFactory{ + errtext: errtext, + name: name, + } + namedFactories[name] = factory + return factory +} + +// FactoryOf return factory of error, return nil if not factory found +func FactoryOf(err error) ErrorFactory { + errobj := castErrorObject(nil, 1, err) + if errobj == nil { + return nil + } + return errobj.Factory() +} + +func (t errorFactory) Error() string { + return t.errtext +} + +func (t errorFactory) Name() string { + return t.name +} + +func (t *errorFactory) New(parent error, params ...interface{}) ErrorObject { + errobj := castErrorObject(t, 1, fmt.Errorf(t.errtext, params...)) + errobj.SetParent(castErrorObject(nil, 1, parent)) + return errobj +} + +func (t *errorFactory) Match(err error) bool { + if t == nil || err == nil { + return false + } + + errcomp := castErrorObject(nil, 1, err) + if errcomp == nil { + return false + } + + return errcomp.Factory() == t +} + +func (t *errorFactory) In(err error) bool { + if t == nil || err == nil { + return false + } + + exist := false + + if errtmp := WalkErrors(castErrorObject(nil, 1, err), func(errcomp ErrorObject) (stop bool, walkerr error) { + if errcomp.Factory() == t { + exist = true + return true, nil + } + return false, nil + }); errtmp != nil { + panic(errtmp) + } + + return exist +} + +var _ ErrorFactory = (*errorFactory)(nil) diff --git a/vendor/github.com/tsaikd/KDGoLib/errutil/ErrorFormatter.go b/vendor/github.com/tsaikd/KDGoLib/errutil/ErrorFormatter.go new file mode 100644 index 00000000..cfdadf95 --- /dev/null +++ b/vendor/github.com/tsaikd/KDGoLib/errutil/ErrorFormatter.go @@ -0,0 +1,12 @@ +package errutil + +// ErrorFormatter to format error +type ErrorFormatter interface { + Format(error) (string, error) +} + +// TraceFormatter to trace error occur line formatter +type TraceFormatter interface { + ErrorFormatter + FormatSkip(errin error, skip int) (string, error) +} diff --git a/vendor/github.com/tsaikd/KDGoLib/errutil/ErrorJSON.go b/vendor/github.com/tsaikd/KDGoLib/errutil/ErrorJSON.go new file mode 100644 index 00000000..6a482bf3 --- /dev/null +++ b/vendor/github.com/tsaikd/KDGoLib/errutil/ErrorJSON.go @@ -0,0 +1,57 @@ +package errutil + +import ( + "strconv" + "strings" +) + +// ErrorJSON is a helper struct for display error +type ErrorJSON struct { + ErrorPath string `json:"errorpath,omitempty"` + ErrorMsg string `json:"error,omitempty"` + ErrorMsgs []string `json:"errors,omitempty"` + ErrorFactories map[string]bool `json:"errfac,omitempty"` +} + +// NewJSON create ErrorJSON +func NewJSON(err error) (*ErrorJSON, error) { + return newJSON(1, err) +} + +func newJSON(skip int, err error) (*ErrorJSON, error) { + errobj := castErrorObject(nil, skip+1, err) + if errobj == nil { + return nil, nil + } + + errors := []string{} + facs := map[string]bool{} + if err := WalkErrors(errobj, func(errcomp ErrorObject) (stop bool, walkerr error) { + errors = append(errors, getErrorText(errcomp)) + factory := errcomp.Factory() + if factory != nil { + if !strings.Contains(factory.Name(), "->") { + facs[factory.Name()] = true + } + } + return false, nil + }); err != nil { + return nil, err + } + + return &ErrorJSON{ + ErrorPath: errobj.PackageName() + "/" + errobj.FileName() + ":" + strconv.Itoa(errobj.Line()), + ErrorMsg: getErrorText(errobj), + ErrorMsgs: errors, + ErrorFactories: facs, + }, nil +} + +func (t *ErrorJSON) Error() string { + if t == nil { + return "" + } + return t.ErrorMsg +} + +var _ error = (*ErrorJSON)(nil) diff --git a/vendor/github.com/tsaikd/KDGoLib/errutil/ErrorObject.go b/vendor/github.com/tsaikd/KDGoLib/errutil/ErrorObject.go new file mode 100644 index 00000000..ee7af3c3 --- /dev/null +++ b/vendor/github.com/tsaikd/KDGoLib/errutil/ErrorObject.go @@ -0,0 +1,133 @@ +package errutil + +import ( + "errors" + + "github.com/tsaikd/KDGoLib/runtimecaller" +) + +// errors +var ( + ErrorWalkLoop = NewFactory("detect error component parents loop when walking") +) + +// New return a new ErrorObject object +func New(text string, errs ...error) ErrorObject { + if text != "" { + errs = append([]error{errors.New(text)}, errs...) + } + return NewErrorsSkip(1, errs...) +} + +// NewErrors return ErrorObject that contains all input errors +func NewErrors(errs ...error) ErrorObject { + return NewErrorsSkip(1, errs...) +} + +// NewErrorsSkip return ErrorObject, skip function call +func NewErrorsSkip(skip int, errs ...error) ErrorObject { + var errcomp ErrorObject + var errtmp ErrorObject + for i, size := 0, len(errs); i < size; i++ { + errtmp = castErrorObject(nil, skip+1, errs[i]) + if errtmp == nil { + continue + } + + if errcomp == nil { + errcomp = errtmp + continue + } + + if err := AddParent(errcomp, errtmp); err != nil { + panic(err) + } + } + return errcomp +} + +// ErrorObject is a rich error interface +type ErrorObject interface { + Error() string + Factory() ErrorFactory + Parent() ErrorObject + SetParent(parent ErrorObject) ErrorObject + runtimecaller.CallInfo +} + +type errorObject struct { + errtext string + factory ErrorFactory + parent ErrorObject + runtimecaller.CallInfo +} + +func castErrorObject(factory ErrorFactory, skip int, err error) ErrorObject { + if err == nil { + return nil + } + switch err.(type) { + case errorObject: + res := err.(errorObject) + return &res + case *errorObject: + return err.(*errorObject) + case ErrorObject: + return err.(ErrorObject) + default: + callinfo, _ := RuntimeCaller(skip + 1) + return &errorObject{ + errtext: err.Error(), + factory: factory, + CallInfo: callinfo, + } + } +} + +func getErrorText(errin error) string { + errobj, ok := errin.(*errorObject) + if ok { + return errobj.errtext + } + return errin.Error() +} + +func (t errorObject) Error() string { + errtext, _ := errorObjectFormatter.Format(&t) + return errtext +} + +func (t *errorObject) Factory() ErrorFactory { + if t == nil { + return nil + } + return t.factory +} + +func (t *errorObject) Parent() ErrorObject { + if t == nil { + return nil + } + return t.parent +} + +func (t *errorObject) SetParent(parent ErrorObject) ErrorObject { + if t == nil { + return nil + } + if t == parent { + return t + } + t.parent = parent + return t +} + +func (t *errorObject) MarshalJSON() ([]byte, error) { + return MarshalJSON(t) +} + +var _ ErrorObject = (*errorObject)(nil) + +var errorObjectFormatter = ErrorFormatter(&ConsoleFormatter{ + Seperator: "; ", +}) diff --git a/vendor/github.com/tsaikd/KDGoLib/errutil/ErrorObjectUtil.go b/vendor/github.com/tsaikd/KDGoLib/errutil/ErrorObjectUtil.go new file mode 100644 index 00000000..9d8bb7b3 --- /dev/null +++ b/vendor/github.com/tsaikd/KDGoLib/errutil/ErrorObjectUtil.go @@ -0,0 +1,88 @@ +package errutil + +import "encoding/json" + +// WalkFunc is a callback for WalkErrors +type WalkFunc func(errcomp ErrorObject) (stop bool, err error) + +// WalkErrors walk from base error through all parents +// return ErrorWalkLoop if detected loop +func WalkErrors(base ErrorObject, walkFunc WalkFunc) (err error) { + if base == nil { + return + } + + loopCheckMap := map[ErrorObject]bool{} + for base != nil { + if _, exist := loopCheckMap[base]; exist { + return ErrorWalkLoop.New(nil) + } + loopCheckMap[base] = true + + stop, walkerr := walkFunc(base) + if walkerr != nil { + return walkerr + } + if stop { + return + } + + base = base.Parent() + } + + return +} + +// Length count number of ErrorObject and all parents, return -1 if error +func Length(base ErrorObject) int { + length := 0 + if err := WalkErrors(base, func(errcomp ErrorObject) (stop bool, walkerr error) { + length++ + return false, nil + }); err != nil { + return -1 + } + return length +} + +// AddParent add parent to errobj +func AddParent(errobj ErrorObject, parent ErrorObject) error { + if errobj == nil || parent == nil { + return nil + } + + // set parent if not exist + if errobj.Parent() == nil { + errobj.SetParent(parent) + return nil + } + + // find oldest parent to set + base := errobj + if err := WalkErrors(base.Parent(), func(errcomp ErrorObject) (stop bool, walkerr error) { + // already in parent tree + if errcomp == parent { + base = nil + return true, nil + } + base = errcomp + return false, nil + }); err != nil { + return err + } + + if base != nil { + base.SetParent(parent) + } + + return nil +} + +// MarshalJSON marshal error to json +func MarshalJSON(errobj error) ([]byte, error) { + errjson, err := newJSON(1, errobj) + if errjson == nil || err != nil { + return []byte(""), err + } + return json.Marshal(errjson) +} diff --git a/vendor/github.com/tsaikd/KDGoLib/errutil/JSONFormatter.go b/vendor/github.com/tsaikd/KDGoLib/errutil/JSONFormatter.go new file mode 100644 index 00000000..0fbceb14 --- /dev/null +++ b/vendor/github.com/tsaikd/KDGoLib/errutil/JSONFormatter.go @@ -0,0 +1,37 @@ +package errutil + +import ( + "bytes" + "encoding/json" +) + +// NewJSONFormatter create JSONFormatter instance +func NewJSONFormatter() *JSONFormatter { + return &JSONFormatter{} +} + +// JSONFormatter used to format error to json +type JSONFormatter struct{} + +// Format error to json +func (t *JSONFormatter) Format(errin error) (errtext string, err error) { + return t.FormatSkip(errin, 1) +} + +// FormatSkip trace error line and format to json +func (t *JSONFormatter) FormatSkip(errin error, skip int) (errtext string, err error) { + errjson, err := newJSON(skip+1, errin) + if errjson == nil || err != nil { + return "", err + } + + buffer := &bytes.Buffer{} + if err = json.NewEncoder(buffer).Encode(errjson); err != nil { + return + } + + return buffer.String(), nil +} + +var _ ErrorFormatter = &JSONFormatter{} +var _ TraceFormatter = &JSONFormatter{} diff --git a/vendor/github.com/tsaikd/KDGoLib/errutil/README.md b/vendor/github.com/tsaikd/KDGoLib/errutil/README.md new file mode 100644 index 00000000..7e5832f0 --- /dev/null +++ b/vendor/github.com/tsaikd/KDGoLib/errutil/README.md @@ -0,0 +1,88 @@ +errutil +======= + +An error handling helper, providing more APIs than built-in package (errors, fmt), and compatible with go error interface + +## Why use errutil instead of built-in errors and fmt + +[![Cover](https://storage.googleapis.com/gcs.milkr.io/topic/255/cover/ea703e50df3a438da92e2f402358c96d37c5c39a)](https://milkr.io/tsaikd/Go-lang-error-handling) + +* https://milkr.io/tsaikd/Go-lang-error-handling + +## Usage + +* Import package from master branch + +``` +import "github.com/tsaikd/KDGoLib/errutil" +``` + +* Declare error factory + +``` +var ErrorOccurWithReason = errutil.NewFactory("An error occur, reason: %q") +``` + +* Return error with factory + +``` +func doSomething() (err error) { + // do something + + // return error with factory, + // first argument is parent error, + // the others are used for factory + return ErrorOccurWithReason.New(nil, "some reason here") +} +``` + +* Handle errors + +``` +func errorHandlingForOneCase() { + if err := doSomething(); err != nil { + if ErrorOccurWithReason.In(err) { + // handling expected error + return + } + + // handling unexpected error + return + } +} +``` + +``` +func errorHandlingForMultipleCases() { + if err := doSomething(); err != nil { + switch errutil.FactoryOf(err) { + case ErrorOccurWithReason: + // handling expected error + return + default: + // handling unexpected error + return + } + } +} +``` + +## Optional usage + +* Import from v1 branch + +``` +import "gopkg.in/tsaikd/KDGoLib.v1/errutil" +``` + +* Use like built-in errors package + * bad case because all errors should be exported for catching by other package + +``` +func doSomething() (err error) { + // do something + + // return error with factory + return errutil.New("An error occur") +} +``` diff --git a/vendor/github.com/tsaikd/KDGoLib/errutil/RuntimeCaller.go b/vendor/github.com/tsaikd/KDGoLib/errutil/RuntimeCaller.go new file mode 100644 index 00000000..0af1b218 --- /dev/null +++ b/vendor/github.com/tsaikd/KDGoLib/errutil/RuntimeCaller.go @@ -0,0 +1,86 @@ +package errutil + +import ( + "bytes" + "io" + "strconv" + "strings" + + "github.com/tsaikd/KDGoLib/runtimecaller" +) + +// DefaultRuntimeCallerFilter use for filter error stack info +var DefaultRuntimeCallerFilter = []runtimecaller.Filter{} + +func init() { + DefaultRuntimeCallerFilter = append( + runtimecaller.FilterCommons, + RuntimeCallerFilterStopErrutilPackage, + RuntimeCallerFilterStopSQLUtilPackage, + ) +} + +// AddRuntimeCallerFilter add filters to DefaultRuntimeCallerFilter for RuntimeCaller() +func AddRuntimeCallerFilter(filters ...runtimecaller.Filter) { + DefaultRuntimeCallerFilter = append(DefaultRuntimeCallerFilter, filters...) +} + +// RuntimeCallerFilterStopErrutilPackage filter CallInfo to stop after reach KDGoLib/errutil package +func RuntimeCallerFilterStopErrutilPackage(callinfo runtimecaller.CallInfo) (valid bool, stop bool) { + if callinfo.PackageName() == "github.com/tsaikd/KDGoLib/errutil" { + return false, true + } + return true, false +} + +// RuntimeCallerFilterStopSQLUtilPackage filter CallInfo to stop after reach KDGoLib/sqlutil package +func RuntimeCallerFilterStopSQLUtilPackage(callinfo runtimecaller.CallInfo) (valid bool, stop bool) { + if callinfo.PackageName() == "github.com/tsaikd/KDGoLib/sqlutil" { + return false, true + } + return true, false +} + +// RuntimeCaller wrap runtimecaller.GetByFilters() with DefaultRuntimeCallerFilter +func RuntimeCaller(skip int, extraFilters ...runtimecaller.Filter) (callinfo runtimecaller.CallInfo, ok bool) { + filters := append(DefaultRuntimeCallerFilter, extraFilters...) + return runtimecaller.GetByFilters(skip+1, filters...) +} + +// WriteCallInfo write readable callinfo with options +func WriteCallInfo( + output io.Writer, + callinfo runtimecaller.CallInfo, + longFile bool, + line bool, + replacePackages map[string]string, +) (n int, err error) { + buffer := &bytes.Buffer{} + if longFile { + pkgname := replacePackage(replacePackages, callinfo.PackageName()) + if _, err = buffer.WriteString(pkgname + "/" + callinfo.FileName()); err != nil { + return + } + } else { + if _, err = buffer.WriteString(callinfo.FileName()); err != nil { + return + } + } + if line { + if _, err = buffer.WriteString(":" + strconv.Itoa(callinfo.Line())); err != nil { + return + } + } + return output.Write([]byte(buffer.String())) +} + +func replacePackage(replacePackages map[string]string, pkgname string) (replaced string) { + replaced = pkgname + if replacePackages == nil { + return + } + for src, tar := range replacePackages { + replaced = strings.Replace(replaced, src, tar, -1) + } + return +} diff --git a/vendor/github.com/tsaikd/KDGoLib/errutil/logger.go b/vendor/github.com/tsaikd/KDGoLib/errutil/logger.go new file mode 100644 index 00000000..64e6c1d6 --- /dev/null +++ b/vendor/github.com/tsaikd/KDGoLib/errutil/logger.go @@ -0,0 +1,127 @@ +package errutil + +import ( + "bytes" + "fmt" + "io" + "os" + "strings" +) + +// LoggerType declare general log types +type LoggerType interface { + Debug(v ...interface{}) + Print(v ...interface{}) + Error(v ...interface{}) + Trace(errin error) + TraceSkip(errin error, skip int) +} + +// Logger return default LoggerType instance +func Logger() LoggerType { + return defaultLogger +} + +// NewLogger create LoggerType instance +func NewLogger(opt LoggerOptions) LoggerType { + opt.check() + return loggerImpl{ + opt: opt, + } +} + +// LoggerOptions for Logger +type LoggerOptions struct { + DefaultOutput io.Writer + ErrorOutput io.Writer + HideFile bool + ShortFile bool + HideLine bool + ReplacePackages map[string]string + TraceFormatter TraceFormatter +} + +func (t *LoggerOptions) check() { + if t.DefaultOutput == nil { + t.DefaultOutput = os.Stdout + } + if t.ErrorOutput == nil { + t.ErrorOutput = os.Stderr + } + if t.TraceFormatter == nil { + t.TraceFormatter = &ConsoleFormatter{ + Seperator: "; ", + TimeFormat: "2006-01-02 15:04:05 ", + LongFile: true, + Line: true, + } + } +} + +var defaultLogger = NewLogger(LoggerOptions{}) + +// SetDefaultLogger set default LoggerType +func SetDefaultLogger(logger LoggerType) { + defaultLogger = logger +} + +type loggerImpl struct { + opt LoggerOptions +} + +func (t loggerImpl) Debug(v ...interface{}) { + t.log(t.opt.DefaultOutput, 1, v...) +} + +func (t loggerImpl) Print(v ...interface{}) { + t.log(t.opt.DefaultOutput, 1, v...) +} + +func (t loggerImpl) Error(v ...interface{}) { + t.log(t.opt.ErrorOutput, 1, v...) +} + +func (t loggerImpl) log(output io.Writer, skip int, v ...interface{}) { + errtext := fmt.Sprint(v...) + if errtext == "" { + return + } + + opt := t.opt + if !opt.HideFile { + buffer := &bytes.Buffer{} + callinfo, _ := RuntimeCaller(skip + 1) + if _, err := WriteCallInfo(buffer, callinfo, !opt.ShortFile, !opt.HideLine, opt.ReplacePackages); err != nil { + panic(err) + } + errtext = buffer.String() + " " + errtext + } + + if !strings.HasSuffix(errtext, "\n") { + errtext += "\n" + } + if _, err := output.Write([]byte(errtext)); err != nil { + panic(err) + } +} + +func (t loggerImpl) Trace(errin error) { + TraceSkip(errin, 1) +} + +func (t loggerImpl) TraceSkip(errin error, skip int) { + var errtext string + var errfmt error + if errtext, errfmt = t.opt.TraceFormatter.FormatSkip(errin, skip+1); errfmt != nil { + panic(errfmt) + } + if errtext == "" { + return + } + if !strings.HasSuffix(errtext, "\n") { + errtext += "\n" + } + if _, errfmt = t.opt.ErrorOutput.Write([]byte(errtext)); errfmt != nil { + panic(errfmt) + } +} diff --git a/vendor/github.com/tsaikd/KDGoLib/errutil/sort.go b/vendor/github.com/tsaikd/KDGoLib/errutil/sort.go new file mode 100644 index 00000000..3b64b824 --- /dev/null +++ b/vendor/github.com/tsaikd/KDGoLib/errutil/sort.go @@ -0,0 +1,27 @@ +package errutil + +func newSorter(factoryMap map[string]ErrorFactory) *sorter { + data := []ErrorFactory{} + for _, factory := range factoryMap { + data = append(data, factory) + } + return &sorter{ + data: data, + } +} + +type sorter struct { + data []ErrorFactory +} + +func (t sorter) Len() int { + return len(t.data) +} + +func (t sorter) Swap(i int, j int) { + t.data[i], t.data[j] = t.data[j], t.data[i] +} + +func (t sorter) Less(i int, j int) bool { + return t.data[i].Name() < t.data[j].Name() +} diff --git a/vendor/github.com/tsaikd/KDGoLib/errutil/trace.go b/vendor/github.com/tsaikd/KDGoLib/errutil/trace.go new file mode 100644 index 00000000..76d51c33 --- /dev/null +++ b/vendor/github.com/tsaikd/KDGoLib/errutil/trace.go @@ -0,0 +1,19 @@ +package errutil + +// Trace error stack, output to default ErrorFormatter, panic if output error +func Trace(errin error) { + TraceSkip(errin, 1) +} + +// TraceWrap trace errin and wrap with wraperr only if errin != nil +func TraceWrap(errin error, wraperr error) { + if errin != nil { + errs := NewErrorsSkip(1, wraperr, errin) + TraceSkip(errs, 1) + } +} + +// TraceSkip error stack, output to default ErrorFormatter, skip n function calls, panic if output error +func TraceSkip(errin error, skip int) { + Logger().TraceSkip(errin, 1) +} diff --git a/vendor/github.com/tsaikd/KDGoLib/errutil/util.go b/vendor/github.com/tsaikd/KDGoLib/errutil/util.go new file mode 100644 index 00000000..6f8ea101 --- /dev/null +++ b/vendor/github.com/tsaikd/KDGoLib/errutil/util.go @@ -0,0 +1,19 @@ +package errutil + +// ContainErrorFunc check error contain error by custom equalFunc +func ContainErrorFunc(err error, equalFunc func(error) bool) bool { + errobj := castErrorObject(nil, 1, err) + contain := false + + if walkerr := WalkErrors(errobj, func(errcomp ErrorObject) (stop bool, walkerr error) { + if equalFunc(errcomp) { + contain = true + return true, nil + } + return false, nil + }); walkerr != nil { + return false + } + + return contain +} diff --git a/vendor/github.com/tsaikd/KDGoLib/logrusutil/ConsoleLogFormatter.go b/vendor/github.com/tsaikd/KDGoLib/logrusutil/ConsoleLogFormatter.go new file mode 100644 index 00000000..4e01be4d --- /dev/null +++ b/vendor/github.com/tsaikd/KDGoLib/logrusutil/ConsoleLogFormatter.go @@ -0,0 +1,106 @@ +package logrusutil + +import ( + "bytes" + "fmt" + "strings" + "time" + + "github.com/sirupsen/logrus" + "github.com/tsaikd/KDGoLib/errutil" + "github.com/tsaikd/KDGoLib/runtimecaller" +) + +// flags +const ( + Llongfile = 1 << iota + Lshortfile + Ltime + Llevel + LstdFlags = Ltime | Lshortfile | Llevel +) + +// ConsoleLogFormatter suitable formatter for console +type ConsoleLogFormatter struct { + TimestampFormat string + Flag int + CallerOffset int + RuntimeCallerFilters []runtimecaller.Filter +} + +func addspace(text string, addspaceflag bool) (string, bool) { + if addspaceflag { + return " " + text, true + } + return text, true +} + +func filterLogrusRuntimeCaller(callinfo runtimecaller.CallInfo) (valid bool, stop bool) { + return !strings.Contains(callinfo.PackageName(), "github.com/sirupsen/logrus"), false +} + +// Format output logrus entry +func (t *ConsoleLogFormatter) Format(entry *logrus.Entry) (data []byte, err error) { + buffer := bytes.Buffer{} + addspaceflag := false + + if t.Flag == 0 { + t.Flag = LstdFlags + } + + if t.TimestampFormat == "" { + t.TimestampFormat = time.RFC3339 + } + + if t.Flag&Ltime != 0 { + timetext := entry.Time.Format(t.TimestampFormat) + timetext, addspaceflag = addspace(timetext, addspaceflag) + if _, err = buffer.WriteString(timetext); err != nil { + err = errutil.New("write timestamp to buffer failed", err) + return + } + } + + if t.Flag&(Lshortfile|Llongfile) != 0 { + var filelinetext string + filters := append([]runtimecaller.Filter{filterLogrusRuntimeCaller}, t.RuntimeCallerFilters...) + if callinfo, ok := errutil.RuntimeCaller(1+t.CallerOffset, filters...); ok { + if t.Flag&Lshortfile != 0 { + filelinetext = fmt.Sprintf("%s:%d", callinfo.FileName(), callinfo.Line()) + } else { + filelinetext = fmt.Sprintf("%s/%s:%d", callinfo.PackageName(), callinfo.FileName(), callinfo.Line()) + } + + filelinetext, addspaceflag = addspace(filelinetext, addspaceflag) + } + + if _, err = buffer.WriteString(filelinetext); err != nil { + err = errutil.New("write fileline to buffer failed", err) + return + } + } + + if t.Flag&Llevel != 0 { + leveltext := fmt.Sprintf("[%s]", entry.Level.String()) + leveltext, addspaceflag = addspace(leveltext, addspaceflag) + if _, err = buffer.WriteString(leveltext); err != nil { + err = errutil.New("write level to buffer failed", err) + return + } + } + + message := entry.Message + message, _ = addspace(message, addspaceflag) + if _, err = buffer.WriteString(message); err != nil { + err = errutil.New("write message to buffer failed", err) + return + } + + if err = buffer.WriteByte('\n'); err != nil { + err = errutil.New("write newline to buffer failed", err) + return + } + + data = buffer.Bytes() + return +} diff --git a/vendor/github.com/tsaikd/KDGoLib/logrusutil/ConsoleLogger.go b/vendor/github.com/tsaikd/KDGoLib/logrusutil/ConsoleLogger.go new file mode 100644 index 00000000..a8321a58 --- /dev/null +++ b/vendor/github.com/tsaikd/KDGoLib/logrusutil/ConsoleLogger.go @@ -0,0 +1,17 @@ +package logrusutil + +import ( + "os" + + "github.com/sirupsen/logrus" +) + +// NewConsoleLogger create new Logger with ConsoleLogFormatter +func NewConsoleLogger() *logrus.Logger { + return &logrus.Logger{ + Out: os.Stdout, + Formatter: &ConsoleLogFormatter{}, + Hooks: make(logrus.LevelHooks), + Level: logrus.InfoLevel, + } +} diff --git a/vendor/github.com/tsaikd/KDGoLib/logrusutil/stack.go b/vendor/github.com/tsaikd/KDGoLib/logrusutil/stack.go new file mode 100644 index 00000000..417db7ac --- /dev/null +++ b/vendor/github.com/tsaikd/KDGoLib/logrusutil/stack.go @@ -0,0 +1,17 @@ +package logrusutil + +import "github.com/sirupsen/logrus" + +// StackLogLevel temporary change log level and return recover function +func StackLogLevel(logger *logrus.Logger, level logrus.Level) (recover func()) { + if logger.Level == level { + return func() {} + } + + originLevel := logger.Level + logger.Level = level + + return func() { + logger.Level = originLevel + } +} diff --git a/vendor/github.com/tsaikd/KDGoLib/runtimecaller/callinfo.go b/vendor/github.com/tsaikd/KDGoLib/runtimecaller/callinfo.go new file mode 100644 index 00000000..a70d6cca --- /dev/null +++ b/vendor/github.com/tsaikd/KDGoLib/runtimecaller/callinfo.go @@ -0,0 +1,75 @@ +package runtimecaller + +import "runtime" + +// CallInfo contains runtime caller information +type CallInfo interface { + // builtin data + PC() uintptr + FilePath() string + Line() int + + // extra info after some process + PCFunc() *runtime.Func + PackageName() string + FileDir() string + FileName() string + FuncName() string +} + +// CallInfoImpl implement CallInfo +type CallInfoImpl struct { + // builtin data + pc uintptr + filePath string + line int + + // extra info after some process + pcFunc *runtime.Func + packageName string + fileDir string + fileName string + funcName string +} + +// PC return CallInfo data +func (t CallInfoImpl) PC() uintptr { + return t.pc +} + +// FilePath return CallInfo data +func (t CallInfoImpl) FilePath() string { + return t.filePath +} + +// Line return CallInfo data +func (t CallInfoImpl) Line() int { + return t.line +} + +// PCFunc return CallInfo data +func (t CallInfoImpl) PCFunc() *runtime.Func { + return t.pcFunc +} + +// PackageName return CallInfo data +func (t CallInfoImpl) PackageName() string { + return t.packageName +} + +// FileDir return CallInfo data +func (t CallInfoImpl) FileDir() string { + return t.fileDir +} + +// FileName return CallInfo data +func (t CallInfoImpl) FileName() string { + return t.fileName +} + +// FuncName return CallInfo data +func (t CallInfoImpl) FuncName() string { + return t.funcName +} + +var _ = CallInfo(CallInfoImpl{}) diff --git a/vendor/github.com/tsaikd/KDGoLib/runtimecaller/filter.go b/vendor/github.com/tsaikd/KDGoLib/runtimecaller/filter.go new file mode 100644 index 00000000..f7eeb11d --- /dev/null +++ b/vendor/github.com/tsaikd/KDGoLib/runtimecaller/filter.go @@ -0,0 +1,26 @@ +package runtimecaller + +import "strings" + +// Filter use to filter runtime.Caller result +type Filter func(callinfo CallInfo) (valid bool, stop bool) + +// FilterCommons contains all common filters +var FilterCommons = []Filter{ + FilterOnlyGoSource, + FilterStopRuntimeCallerPackage, +} + +// FilterOnlyGoSource filter CallInfo FileName end with ".go" +func FilterOnlyGoSource(callinfo CallInfo) (valid bool, stop bool) { + filename := strings.ToLower(callinfo.FileName()) + return strings.HasSuffix(filename, ".go"), false +} + +// FilterStopRuntimeCallerPackage filter CallInfo to stop after reach KDGoLib/runtimecaller package +func FilterStopRuntimeCallerPackage(callinfo CallInfo) (valid bool, stop bool) { + if callinfo.PackageName() == "github.com/tsaikd/KDGoLib/runtimecaller" { + return false, true + } + return true, false +} diff --git a/vendor/github.com/tsaikd/KDGoLib/runtimecaller/runtimeCaller.go b/vendor/github.com/tsaikd/KDGoLib/runtimecaller/runtimeCaller.go new file mode 100644 index 00000000..c6e70746 --- /dev/null +++ b/vendor/github.com/tsaikd/KDGoLib/runtimecaller/runtimeCaller.go @@ -0,0 +1,92 @@ +package runtimecaller + +import ( + "path" + "runtime" + "strings" +) + +// GetByFilters return CallInfo until all filters are valid +func GetByFilters(skip int, filters ...Filter) (callinfo CallInfo, ok bool) { + filters = append(FilterCommons, filters...) + for { + skip++ + + if callinfo, ok = retrieveCallInfo(skip); !ok { + return + } + + valid, stop := filterAll(callinfo, filters...) + if valid { + return callinfo, true + } + if stop { + return callinfo, false + } + } +} + +// ListByFilters return all CallInfo stack for all filters are valid +func ListByFilters(skip int, filters ...Filter) (callinfos []CallInfo) { + filters = append(FilterCommons, filters...) + for { + var callinfo CallInfo + var ok bool + skip++ + + if callinfo, ok = retrieveCallInfo(skip); !ok { + return + } + + valid, stop := filterAll(callinfo, filters...) + if valid { + callinfos = append(callinfos, callinfo) + } + if stop { + return + } + } +} + +// http://stackoverflow.com/questions/25262754/how-to-get-name-of-current-package-in-go +func retrieveCallInfo(skip int) (result CallInfo, ok bool) { + callinfo := CallInfoImpl{} + + if callinfo.pc, callinfo.filePath, callinfo.line, ok = runtime.Caller(skip + 1); !ok { + return + } + + callinfo.fileDir, callinfo.fileName = path.Split(callinfo.filePath) + callinfo.pcFunc = runtime.FuncForPC(callinfo.pc) + + parts := strings.Split(callinfo.pcFunc.Name(), ".") + pl := len(parts) + if pl < 1 { + return result, false + } + callinfo.funcName = parts[pl-1] + + if pl >= 2 && parts[pl-2] != "" && parts[pl-2][0] == '(' { + callinfo.funcName = parts[pl-2] + "." + callinfo.funcName + callinfo.packageName = strings.Join(parts[0:pl-2], ".") + } else { + callinfo.packageName = strings.Join(parts[0:pl-1], ".") + } + + return callinfo, true +} + +func filterAll(callinfo CallInfo, filters ...Filter) (allvalid bool, onestop bool) { + allvalid = true + for _, filter := range filters { + valid, stop := filter(callinfo) + allvalid = allvalid && valid + if stop { + return allvalid, true + } + if !allvalid { + return + } + } + return +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 06fda0d4..248eb118 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -23,6 +23,11 @@ github.com/sirupsen/logrus/hooks/test ## explicit github.com/stretchr/testify/assert github.com/stretchr/testify/require +# github.com/tsaikd/KDGoLib v0.0.0-20191001134900-7f3cf518e07d +## explicit +github.com/tsaikd/KDGoLib/errutil +github.com/tsaikd/KDGoLib/logrusutil +github.com/tsaikd/KDGoLib/runtimecaller # golang.org/x/mod v0.3.0 ## explicit golang.org/x/mod/semver