Skip to content
This repository was archived by the owner on Jan 25, 2022. It is now read-only.

Commit d2b9e4c

Browse files
glyntotherme
authored andcommitted
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 <[email protected]>
1 parent a3ba0d6 commit d2b9e4c

File tree

4 files changed

+308
-2
lines changed

4 files changed

+308
-2
lines changed

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
1-
# nocs
2-
A containerless implementation of the OCI Open Container Specification
1+
# nOCS
2+
A containerless implementation of the OCI Open Container Specification.
3+
4+
This is intended for testing [runc](https://github.com/opencontainers/runc), and other
5+
[OCF](https://github.com/opencontainers/specs) clients without creating containers.

main.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io/ioutil"
7+
"os"
8+
"os/exec"
9+
"path"
10+
"path/filepath"
11+
"syscall"
12+
13+
"github.com/cf-guardian/specs"
14+
)
15+
16+
func main() {
17+
if os.Args[1] != "exec" {
18+
panic("Unsupported command!")
19+
}
20+
config := parseConfig(configFilePath())
21+
22+
cmd := exec.Command(config.Process.Args[0], config.Process.Args[1:]...)
23+
cmd.Stdout = os.Stdout
24+
cmd.Stderr = os.Stderr
25+
26+
checkError(cmd.Start(), "starting process", 80)
27+
28+
checkWaitError(cmd.Wait(), "awaiting process completion", 85)
29+
}
30+
31+
func configFilePath() string {
32+
if len(os.Args) < 3 || os.Args[2] == "" {
33+
pwd, err := os.Getwd()
34+
checkError(err, "getting working directory", 90)
35+
return path.Join(pwd, "config.json")
36+
}
37+
return os.Args[2]
38+
}
39+
40+
func parseConfig(configPath string) *specs.Spec {
41+
configStr, err := ioutil.ReadFile(configPath)
42+
checkError(err, "reading config file", 95)
43+
44+
var config = &specs.Spec{}
45+
checkError(json.Unmarshal([]byte(configStr), config), "parsing config JSON", 100)
46+
47+
return config
48+
}
49+
50+
func checkWaitError(err error, action string, exitCode int) {
51+
if err != nil {
52+
if exitErr, ok := err.(*exec.ExitError); ok {
53+
if waitStatus, ok := exitErr.Sys().(syscall.WaitStatus); !ok {
54+
checkError(err, action+": expected a WaitStatus", exitCode)
55+
} else {
56+
os.Exit(waitStatus.ExitStatus())
57+
}
58+
}
59+
checkError(err, action+": expected an ExitError", exitCode)
60+
}
61+
}
62+
63+
func checkError(err error, action string, exitCode int) {
64+
if err != nil {
65+
fmt.Fprintf(os.Stderr, "%s: %s: %s\n", filepath.Base(os.Args[0]), action, err)
66+
os.Exit(exitCode)
67+
}
68+
}

nocs_suite_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package main_test
2+
3+
import (
4+
. "github.com/onsi/ginkgo"
5+
. "github.com/onsi/gomega"
6+
7+
"testing"
8+
9+
"github.com/onsi/gomega/gexec"
10+
)
11+
12+
var nocsBin string
13+
14+
func TestNocs(t *testing.T) {
15+
RegisterFailHandler(Fail)
16+
17+
SynchronizedBeforeSuite(func() []byte {
18+
nocsPath, err := gexec.Build("github.com/cloudfoundry-incubator/nocs")
19+
Expect(err).ToNot(HaveOccurred())
20+
return []byte(nocsPath)
21+
}, func(path []byte) {
22+
Expect(string(path)).NotTo(BeEmpty())
23+
nocsBin = string(path)
24+
})
25+
26+
RunSpecs(t, "nOCS Suite")
27+
}

nocs_test.go

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
package main_test
2+
3+
import (
4+
"bytes"
5+
"io/ioutil"
6+
"os"
7+
"os/exec"
8+
"path"
9+
"strings"
10+
"syscall"
11+
12+
"path/filepath"
13+
14+
. "github.com/onsi/ginkgo"
15+
. "github.com/onsi/gomega"
16+
)
17+
18+
var _ = Describe("nOCS", func() {
19+
var (
20+
configFileDir string
21+
nocsProcessWD string
22+
23+
cmd *exec.Cmd
24+
25+
stdout, stderr string
26+
exitCode int
27+
)
28+
29+
BeforeEach(func() {
30+
dirtyPath, err := ioutil.TempDir("", "nOCSTest")
31+
Expect(err).NotTo(HaveOccurred())
32+
configFileDir, err = filepath.EvalSymlinks(dirtyPath)
33+
Expect(err).NotTo(HaveOccurred())
34+
35+
nocsProcessWD, err = os.Getwd()
36+
Expect(err).NotTo(HaveOccurred())
37+
})
38+
39+
JustBeforeEach(func() {
40+
cmd.Dir = nocsProcessWD
41+
42+
outBuffer, errBuffer := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
43+
cmd.Stdout, cmd.Stderr = outBuffer, errBuffer
44+
45+
Expect(cmd.Start()).To(Succeed())
46+
47+
exitCode = getExitCode(cmd.Wait())
48+
49+
stdout, stderr = outBuffer.String(), errBuffer.String()
50+
})
51+
52+
AfterEach(func() {
53+
os.RemoveAll(configFileDir)
54+
})
55+
56+
Context("Running a simple command", func() {
57+
BeforeEach(func() {
58+
commandArgs := []string{"echo", "Hello OCF!"}
59+
configFilePath := createConfigFile("nocs-simple.json", configFileDir, commandArgs)
60+
61+
cmd = exec.Command(nocsBin, "exec", configFilePath)
62+
})
63+
64+
It("produces the appropriate stdout", func() {
65+
Expect(stdout).To(Equal("Hello OCF!\n"))
66+
})
67+
68+
It("produces the appropriate stderr", func() {
69+
Expect(stderr).To(BeEmpty())
70+
})
71+
72+
It("returns the appropriate exit status code", func() {
73+
Expect(exitCode).To(Equal(0))
74+
})
75+
})
76+
77+
Context("Running a command which produces stderr", func() {
78+
BeforeEach(func() {
79+
commandArgs := []string{"/bin/sh", "-c", "echo Hello OCF! 1>&2"}
80+
configFilePath := createConfigFile("nocs-stderr.json", configFileDir, commandArgs)
81+
82+
cmd = exec.Command(nocsBin, "exec", configFilePath)
83+
})
84+
85+
It("produces the appropriate stdout", func() {
86+
Expect(stdout).To(BeEmpty())
87+
})
88+
89+
It("produces the appropriate stderr", func() {
90+
Expect(stderr).To(Equal("Hello OCF!\n"))
91+
})
92+
93+
It("returns the appropriate exit status code", func() {
94+
Expect(exitCode).To(Equal(0))
95+
})
96+
})
97+
98+
Context("Running a command which exits with non-zero status", func() {
99+
BeforeEach(func() {
100+
commandArgs := []string{"/usr/bin/false"}
101+
configFilePath := createConfigFile("nocs-errcode.json", configFileDir, commandArgs)
102+
103+
cmd = exec.Command(nocsBin, "exec", configFilePath)
104+
})
105+
106+
It("returns the appropriate exit status code", func() {
107+
Expect(exitCode).To(Equal(1))
108+
})
109+
})
110+
111+
Context("Running without passing a configuration filepath", func() {
112+
BeforeEach(func() {
113+
commandArgs := []string{"echo", "Hello default OCF!"}
114+
createConfigFile("config.json", configFileDir, commandArgs)
115+
116+
cmd = exec.Command(nocsBin, "exec")
117+
118+
nocsProcessWD = configFileDir
119+
})
120+
121+
It("produces the appropriate stdout", func() {
122+
Expect(stdout).To(Equal("Hello default OCF!\n"))
123+
})
124+
})
125+
126+
Context("Running when passing the empty configuration filepath", func() {
127+
BeforeEach(func() {
128+
commandArgs := []string{"echo", "Hello empty OCF!"}
129+
createConfigFile("config.json", configFileDir, commandArgs)
130+
131+
cmd = exec.Command(nocsBin, "exec", "")
132+
133+
nocsProcessWD = configFileDir
134+
})
135+
136+
It("produces the appropriate stdout", func() {
137+
Expect(stdout).To(Equal("Hello empty OCF!\n"))
138+
})
139+
})
140+
141+
Context("Running with a missing configuration file", func() {
142+
BeforeEach(func() {
143+
cmd = exec.Command(nocsBin, "exec")
144+
145+
nocsProcessWD = configFileDir
146+
})
147+
148+
It("produces the appropriate stderr", func() {
149+
Expect(stderr).To(Equal(filepath.Base(nocsBin) + ": reading config file: open " + filepath.Join(configFileDir, "config.json: no such file or directory\n")))
150+
})
151+
152+
It("returns the appropriate exit status code", func() {
153+
Expect(exitCode).To(Equal(95))
154+
})
155+
156+
})
157+
158+
Context("Running when passing a relative configuration filepath", func() {
159+
BeforeEach(func() {
160+
commandArgs := []string{"echo", "Hello relative OCF!"}
161+
createConfigFile("relative.json", configFileDir, commandArgs)
162+
subdirPath := filepath.Join(configFileDir, "some-subdir")
163+
err := os.Mkdir(subdirPath, 0777)
164+
Expect(err).NotTo(HaveOccurred())
165+
166+
cmd = exec.Command(nocsBin, "exec", "../relative.json")
167+
168+
nocsProcessWD = subdirPath
169+
})
170+
171+
It("produces the appropriate stdout", func() {
172+
Expect(stdout).To(Equal("Hello relative OCF!\n"))
173+
})
174+
})
175+
})
176+
177+
func createConfigFile(configFileName, configFileDir string, args []string) string {
178+
configFilePath := path.Join(configFileDir, configFileName)
179+
180+
err := ioutil.WriteFile(configFilePath, []byte(
181+
`{"process": {
182+
"user": {
183+
"uid": 1,
184+
"gid": 1
185+
},
186+
"args": `+argsToJSON(args)+`
187+
}}`), 0777)
188+
Expect(err).NotTo(HaveOccurred())
189+
190+
return configFilePath
191+
}
192+
193+
func argsToJSON(args []string) string {
194+
return `["` + strings.Join(args, `","`) + `"]`
195+
}
196+
197+
func getExitCode(err error) int {
198+
if err != nil {
199+
exitErr, ok := err.(*exec.ExitError)
200+
Expect(ok).To(BeTrue())
201+
202+
waitStatus, ok := exitErr.Sys().(syscall.WaitStatus)
203+
Expect(ok).To(BeTrue())
204+
205+
return waitStatus.ExitStatus()
206+
}
207+
return 0
208+
}

0 commit comments

Comments
 (0)