Skip to content

Commit

Permalink
Merge pull request #16 from Vicente-Cheng/add-command-ns
Browse files Browse the repository at this point in the history
Add command and ns libs
  • Loading branch information
bk201 authored Jul 9, 2024
2 parents c1208a4 + 2ef2139 commit 22ebbe4
Show file tree
Hide file tree
Showing 20 changed files with 296 additions and 39 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.21
go-version: 1.22

- name: lint
run: |
Expand All @@ -30,5 +30,5 @@ jobs:
- name: Build
run: go build -v ./...

- name: Test
run: go test -v ./...
- name: CI
run: make ci
2 changes: 1 addition & 1 deletion Dockerfile.dapper
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM registry.suse.com/bci/golang:1.21
FROM registry.suse.com/bci/golang:1.22

ARG DAPPER_HOST_ARCH
ENV HOST_ARCH=${DAPPER_HOST_ARCH} ARCH=${DAPPER_HOST_ARCH}
Expand Down
90 changes: 90 additions & 0 deletions command/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package command

import (
"bytes"
"os/exec"
"path/filepath"
"time"

"github.com/pkg/errors"
)

var ErrCmdTimeout = errors.New("command timeout")

const (
NSBinary = "nsenter"
cmdTimeoutDefault = 180 * time.Second // 3 minutes by default
cmdTimeoutNone = 0 * time.Second // no timeout
)

type Executor struct {
namespace string
cmdTimeout time.Duration
}

func NewExecutor() *Executor {
return &Executor{
namespace: "",
cmdTimeout: cmdTimeoutDefault,
}
}

func NewExecutorWithNS(ns string) (*Executor, error) {
exec := NewExecutor()
exec.namespace = ns

// test if nsenter is available
if _, err := execute(NSBinary, []string{"-V"}, cmdTimeoutNone); err != nil {
return nil, errors.Wrap(err, "cannot find nsenter for namespace switching")
}
return exec, nil
}

func (exec *Executor) SetTimeout(timeout time.Duration) {
exec.cmdTimeout = timeout
}

func (exec *Executor) Execute(cmd string, args []string) (string, error) {
command := cmd
cmdArgs := args
if exec.namespace != "" {
cmdArgs = []string{
"--mount=" + filepath.Join(exec.namespace, "mnt"),
"--net=" + filepath.Join(exec.namespace, "net"),
"--ipc=" + filepath.Join(exec.namespace, "ipc"),
cmd,
}
command = NSBinary
cmdArgs = append(cmdArgs, args...)
}
return execute(command, cmdArgs, exec.cmdTimeout)
}

func execute(command string, args []string, timeout time.Duration) (string, error) {
cmd := exec.Command(command, args...)

var output, stderr bytes.Buffer
cmdTimeout := false
cmd.Stdout = &output
cmd.Stderr = &stderr

timer := time.NewTimer(cmdTimeoutNone)
if timeout != cmdTimeoutNone {
// add timer to kill the process if timeout
timer = time.AfterFunc(timeout, func() {
cmdTimeout = true
cmd.Process.Kill()
})
}
defer timer.Stop()

if err := cmd.Run(); err != nil {
if cmdTimeout {
return "", errors.Wrapf(ErrCmdTimeout, "timeout after %v: %v %v", timeout, command, args)
}
return "", errors.Wrapf(err, "failed to execute: %v %v, output %s, stderr %s",
command, args, output.String(), stderr.String())
}

return output.String(), nil
}
46 changes: 46 additions & 0 deletions command/command_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package command

import (
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)

type CommandTestSuite struct {
suite.Suite
executor *Executor
}

func (suite *CommandTestSuite) SetupTest() {
suite.executor = NewExecutor()
}

func (suite *CommandTestSuite) TestNewExecutor() {
assert.NotNil(suite.T(), suite.executor)
}

func (suite *CommandTestSuite) TestSetTimeout() {
suite.executor.SetTimeout(5 * time.Second)
assert.Equal(suite.T(), 5*time.Second, suite.executor.cmdTimeout)
}

func (suite *CommandTestSuite) TestExecute() {
output, err := suite.executor.Execute("echo", []string{"hello", "world"})
assert.NoError(suite.T(), err)
assert.Equal(suite.T(), "hello world\n", output)
}

func (suite *CommandTestSuite) TestExecute_Timeout() {
suite.executor.SetTimeout(1 * time.Second)
assert.Equal(suite.T(), 1*time.Second, suite.executor.cmdTimeout)
output, err := suite.executor.Execute("sleep", []string{"5"})
assert.NotNil(suite.T(), err)
assert.Contains(suite.T(), err.Error(), "command timeout")
assert.Empty(suite.T(), output)
}

func TestCommandTestSuite(t *testing.T) {
suite.Run(t, new(CommandTestSuite))
}
73 changes: 73 additions & 0 deletions common/ns.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package common

import (
"fmt"

"github.com/prometheus/procfs"
)

const (
DockerdProcess = "dockerd"
ContainerdProcess = "containerd"
ContainerdProcessShim = "containerd-shim"
)

func getPidProc(hostProcPath string, pid int) (*procfs.Proc, error) {
fs, err := procfs.NewFS(hostProcPath)
if err != nil {
return nil, err
}
proc, err := fs.Proc(pid)
if err != nil {
return nil, err
}
return &proc, nil
}

func getSelfProc(hostProcPath string) (*procfs.Proc, error) {
fs, err := procfs.NewFS(hostProcPath)
if err != nil {
return nil, err
}
proc, err := fs.Self()
if err != nil {
return nil, err
}
return &proc, nil
}

func findAncestorByName(hostProcPath string, ancestorProcess string) (*procfs.Proc, error) {
proc, err := getSelfProc(hostProcPath)
if err != nil {
return nil, err
}

for {
st, err := proc.Stat()
if err != nil {
return nil, err
}
if st.Comm == ancestorProcess {
return proc, nil
}
if st.PPID == 0 {
break
}
proc, err = getPidProc(hostProcPath, st.PPID)
if err != nil {
return nil, err
}
}
return nil, fmt.Errorf("failed to find the ancestor process: %s", ancestorProcess)
}

func GetHostNamespacePath(hostProcPath string) string {
containerNames := []string{DockerdProcess, ContainerdProcess, ContainerdProcessShim}
for _, name := range containerNames {
proc, err := findAncestorByName(hostProcPath, name)
if err == nil {
return fmt.Sprintf("%s/%d/ns/", hostProcPath, proc.PID)
}
}
return fmt.Sprintf("%s/%d/ns/", hostProcPath, 1)
}
52 changes: 52 additions & 0 deletions common/ns_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package common

import (
"os"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)

type NSTestSuite struct {
suite.Suite
hostProcPath string
}

func (suite *NSTestSuite) SetupTest() {
suite.hostProcPath = "/proc"
}

func TestNSTestSuite(t *testing.T) {
suite.Run(t, new(NSTestSuite))
}

func (suite *NSTestSuite) TestGetPidProc() {
pid := 66666 // should not exists
proc, err := getPidProc(suite.hostProcPath, pid)

assert.NotNil(suite.T(), err)
assert.Nil(suite.T(), proc)

pid = 1
proc, err = getPidProc(suite.hostProcPath, pid)

assert.NoError(suite.T(), err)
assert.NotNil(suite.T(), proc)
assert.Equal(suite.T(), pid, proc.PID)
}

func (suite *NSTestSuite) TestGetSelfProc() {
proc, err := getSelfProc(suite.hostProcPath)

assert.NoError(suite.T(), err)
assert.NotNil(suite.T(), proc)
assert.Equal(suite.T(), os.Getpid(), proc.PID)
}

func (suite *NSTestSuite) TestGetHostNamespacePath() {
expectedPath := "/proc/1/ns/"
path := GetHostNamespacePath(suite.hostProcPath)

assert.Equal(suite.T(), expectedPath, path)
}
2 changes: 1 addition & 1 deletion random.go → common/random.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package gocommon
package common

import (
"crypto/rand"
Expand Down
2 changes: 1 addition & 1 deletion random_test.go → common/random_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package gocommon
package common

import (
"testing"
Expand Down
2 changes: 1 addition & 1 deletion map.go → ds/map.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package gocommon
package ds

// MapFilterFunc iterates over elements of map, returning a map of all elements
// the function f returns truthy for. The function f is invoked with three
Expand Down
2 changes: 1 addition & 1 deletion map_test.go → ds/map_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package gocommon
package ds

import (
"testing"
Expand Down
2 changes: 1 addition & 1 deletion slice.go → ds/slice.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package gocommon
package ds

import "slices"

Expand Down
2 changes: 1 addition & 1 deletion slice_test.go → ds/slice_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package gocommon
package ds

import (
"strings"
Expand Down
2 changes: 1 addition & 1 deletion files.go → files/files.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package gocommon
package files

import (
"fmt"
Expand Down
8 changes: 6 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/harvester/go-common

go 1.21
go 1.22

require (
github.com/coreos/go-systemd/v22 v22.5.0
Expand All @@ -11,9 +11,13 @@ require (
gopkg.in/yaml.v3 v3.0.1
)

require github.com/prometheus/procfs v0.15.1

require github.com/pkg/errors v0.9.1

require (
github.com/Masterminds/semver/v3 v3.2.1
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sys v0.0.0-20220908164124-27713097b956 // indirect
golang.org/x/sys v0.20.0 // indirect
)
9 changes: 8 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,23 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y=
github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956 h1:XeJjHH1KiLpKGb6lvMiksZ9l0fVUh+AmGcm0nOMEBOY=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
Loading

0 comments on commit 22ebbe4

Please sign in to comment.