This repository has been archived by the owner on Jan 25, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial implementation: nocs exec can run echo.
We currently depend on https://github.com/cf-guardian/specs which is a fork of https://github.com/opencontainers/specs and which may be superseded by the pull request opencontainers/runtime-spec#135. [#101501242] Signed-off-by: Gareth Smith <gsmith@pivotal.io>
- Loading branch information
Showing
4 changed files
with
308 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,5 @@ | ||
# nocs | ||
A containerless implementation of the OCI Open Container Specification | ||
# nOCS | ||
A containerless implementation of the OCI Open Container Specification. | ||
|
||
This is intended for testing [runc](https://github.com/opencontainers/runc), and other | ||
[OCF](https://github.com/opencontainers/specs) clients without creating containers. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io/ioutil" | ||
"os" | ||
"os/exec" | ||
"path" | ||
"path/filepath" | ||
"syscall" | ||
|
||
"github.com/cf-guardian/specs" | ||
) | ||
|
||
func main() { | ||
if os.Args[1] != "exec" { | ||
panic("Unsupported command!") | ||
} | ||
config := parseConfig(configFilePath()) | ||
|
||
cmd := exec.Command(config.Process.Args[0], config.Process.Args[1:]...) | ||
cmd.Stdout = os.Stdout | ||
cmd.Stderr = os.Stderr | ||
|
||
checkError(cmd.Start(), "starting process", 80) | ||
|
||
checkWaitError(cmd.Wait(), "awaiting process completion", 85) | ||
} | ||
|
||
func configFilePath() string { | ||
if len(os.Args) < 3 || os.Args[2] == "" { | ||
pwd, err := os.Getwd() | ||
checkError(err, "getting working directory", 90) | ||
return path.Join(pwd, "config.json") | ||
} | ||
return os.Args[2] | ||
} | ||
|
||
func parseConfig(configPath string) *specs.Spec { | ||
configStr, err := ioutil.ReadFile(configPath) | ||
checkError(err, "reading config file", 95) | ||
|
||
var config = &specs.Spec{} | ||
checkError(json.Unmarshal([]byte(configStr), config), "parsing config JSON", 100) | ||
|
||
return config | ||
} | ||
|
||
func checkWaitError(err error, action string, exitCode int) { | ||
if err != nil { | ||
if exitErr, ok := err.(*exec.ExitError); ok { | ||
if waitStatus, ok := exitErr.Sys().(syscall.WaitStatus); !ok { | ||
checkError(err, action+": expected a WaitStatus", exitCode) | ||
} else { | ||
os.Exit(waitStatus.ExitStatus()) | ||
} | ||
} | ||
checkError(err, action+": expected an ExitError", exitCode) | ||
} | ||
} | ||
|
||
func checkError(err error, action string, exitCode int) { | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "%s: %s: %s\n", filepath.Base(os.Args[0]), action, err) | ||
os.Exit(exitCode) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package main_test | ||
|
||
import ( | ||
. "github.com/onsi/ginkgo" | ||
. "github.com/onsi/gomega" | ||
|
||
"testing" | ||
|
||
"github.com/onsi/gomega/gexec" | ||
) | ||
|
||
var nocsBin string | ||
|
||
func TestNocs(t *testing.T) { | ||
RegisterFailHandler(Fail) | ||
|
||
SynchronizedBeforeSuite(func() []byte { | ||
nocsPath, err := gexec.Build("github.com/cloudfoundry-incubator/nocs") | ||
Expect(err).ToNot(HaveOccurred()) | ||
return []byte(nocsPath) | ||
}, func(path []byte) { | ||
Expect(string(path)).NotTo(BeEmpty()) | ||
nocsBin = string(path) | ||
}) | ||
|
||
RunSpecs(t, "nOCS Suite") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
package main_test | ||
|
||
import ( | ||
"bytes" | ||
"io/ioutil" | ||
"os" | ||
"os/exec" | ||
"path" | ||
"strings" | ||
"syscall" | ||
|
||
"path/filepath" | ||
|
||
. "github.com/onsi/ginkgo" | ||
. "github.com/onsi/gomega" | ||
) | ||
|
||
var _ = Describe("nOCS", func() { | ||
var ( | ||
configFileDir string | ||
nocsProcessWD string | ||
|
||
cmd *exec.Cmd | ||
|
||
stdout, stderr string | ||
exitCode int | ||
) | ||
|
||
BeforeEach(func() { | ||
dirtyPath, err := ioutil.TempDir("", "nOCSTest") | ||
Expect(err).NotTo(HaveOccurred()) | ||
configFileDir, err = filepath.EvalSymlinks(dirtyPath) | ||
Expect(err).NotTo(HaveOccurred()) | ||
|
||
nocsProcessWD, err = os.Getwd() | ||
Expect(err).NotTo(HaveOccurred()) | ||
}) | ||
|
||
JustBeforeEach(func() { | ||
cmd.Dir = nocsProcessWD | ||
|
||
outBuffer, errBuffer := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{}) | ||
cmd.Stdout, cmd.Stderr = outBuffer, errBuffer | ||
|
||
Expect(cmd.Start()).To(Succeed()) | ||
|
||
exitCode = getExitCode(cmd.Wait()) | ||
|
||
stdout, stderr = outBuffer.String(), errBuffer.String() | ||
}) | ||
|
||
AfterEach(func() { | ||
os.RemoveAll(configFileDir) | ||
}) | ||
|
||
Context("Running a simple command", func() { | ||
BeforeEach(func() { | ||
commandArgs := []string{"echo", "Hello OCF!"} | ||
configFilePath := createConfigFile("nocs-simple.json", configFileDir, commandArgs) | ||
|
||
cmd = exec.Command(nocsBin, "exec", configFilePath) | ||
}) | ||
|
||
It("produces the appropriate stdout", func() { | ||
Expect(stdout).To(Equal("Hello OCF!\n")) | ||
}) | ||
|
||
It("produces the appropriate stderr", func() { | ||
Expect(stderr).To(BeEmpty()) | ||
}) | ||
|
||
It("returns the appropriate exit status code", func() { | ||
Expect(exitCode).To(Equal(0)) | ||
}) | ||
}) | ||
|
||
Context("Running a command which produces stderr", func() { | ||
BeforeEach(func() { | ||
commandArgs := []string{"/bin/sh", "-c", "echo Hello OCF! 1>&2"} | ||
configFilePath := createConfigFile("nocs-stderr.json", configFileDir, commandArgs) | ||
|
||
cmd = exec.Command(nocsBin, "exec", configFilePath) | ||
}) | ||
|
||
It("produces the appropriate stdout", func() { | ||
Expect(stdout).To(BeEmpty()) | ||
}) | ||
|
||
It("produces the appropriate stderr", func() { | ||
Expect(stderr).To(Equal("Hello OCF!\n")) | ||
}) | ||
|
||
It("returns the appropriate exit status code", func() { | ||
Expect(exitCode).To(Equal(0)) | ||
}) | ||
}) | ||
|
||
Context("Running a command which exits with non-zero status", func() { | ||
BeforeEach(func() { | ||
commandArgs := []string{"/usr/bin/false"} | ||
configFilePath := createConfigFile("nocs-errcode.json", configFileDir, commandArgs) | ||
|
||
cmd = exec.Command(nocsBin, "exec", configFilePath) | ||
}) | ||
|
||
It("returns the appropriate exit status code", func() { | ||
Expect(exitCode).To(Equal(1)) | ||
}) | ||
}) | ||
|
||
Context("Running without passing a configuration filepath", func() { | ||
BeforeEach(func() { | ||
commandArgs := []string{"echo", "Hello default OCF!"} | ||
createConfigFile("config.json", configFileDir, commandArgs) | ||
|
||
cmd = exec.Command(nocsBin, "exec") | ||
|
||
nocsProcessWD = configFileDir | ||
}) | ||
|
||
It("produces the appropriate stdout", func() { | ||
Expect(stdout).To(Equal("Hello default OCF!\n")) | ||
}) | ||
}) | ||
|
||
Context("Running when passing the empty configuration filepath", func() { | ||
BeforeEach(func() { | ||
commandArgs := []string{"echo", "Hello empty OCF!"} | ||
createConfigFile("config.json", configFileDir, commandArgs) | ||
|
||
cmd = exec.Command(nocsBin, "exec", "") | ||
|
||
nocsProcessWD = configFileDir | ||
}) | ||
|
||
It("produces the appropriate stdout", func() { | ||
Expect(stdout).To(Equal("Hello empty OCF!\n")) | ||
}) | ||
}) | ||
|
||
Context("Running with a missing configuration file", func() { | ||
BeforeEach(func() { | ||
cmd = exec.Command(nocsBin, "exec") | ||
|
||
nocsProcessWD = configFileDir | ||
}) | ||
|
||
It("produces the appropriate stderr", func() { | ||
Expect(stderr).To(Equal(filepath.Base(nocsBin) + ": reading config file: open " + filepath.Join(configFileDir, "config.json: no such file or directory\n"))) | ||
}) | ||
|
||
It("returns the appropriate exit status code", func() { | ||
Expect(exitCode).To(Equal(95)) | ||
}) | ||
|
||
}) | ||
|
||
Context("Running when passing a relative configuration filepath", func() { | ||
BeforeEach(func() { | ||
commandArgs := []string{"echo", "Hello relative OCF!"} | ||
createConfigFile("relative.json", configFileDir, commandArgs) | ||
subdirPath := filepath.Join(configFileDir, "some-subdir") | ||
err := os.Mkdir(subdirPath, 0777) | ||
Expect(err).NotTo(HaveOccurred()) | ||
|
||
cmd = exec.Command(nocsBin, "exec", "../relative.json") | ||
|
||
nocsProcessWD = subdirPath | ||
}) | ||
|
||
It("produces the appropriate stdout", func() { | ||
Expect(stdout).To(Equal("Hello relative OCF!\n")) | ||
}) | ||
}) | ||
}) | ||
|
||
func createConfigFile(configFileName, configFileDir string, args []string) string { | ||
configFilePath := path.Join(configFileDir, configFileName) | ||
|
||
err := ioutil.WriteFile(configFilePath, []byte( | ||
`{"process": { | ||
"user": { | ||
"uid": 1, | ||
"gid": 1 | ||
}, | ||
"args": `+argsToJSON(args)+` | ||
}}`), 0777) | ||
Expect(err).NotTo(HaveOccurred()) | ||
|
||
return configFilePath | ||
} | ||
|
||
func argsToJSON(args []string) string { | ||
return `["` + strings.Join(args, `","`) + `"]` | ||
} | ||
|
||
func getExitCode(err error) int { | ||
if err != nil { | ||
exitErr, ok := err.(*exec.ExitError) | ||
Expect(ok).To(BeTrue()) | ||
|
||
waitStatus, ok := exitErr.Sys().(syscall.WaitStatus) | ||
Expect(ok).To(BeTrue()) | ||
|
||
return waitStatus.ExitStatus() | ||
} | ||
return 0 | ||
} |