mirror of
https://github.com/qdm12/gluetun.git
synced 2025-12-10 10:45:38 -06:00
chore(deps): implement github.com/qdm12/golibs/command locally (#2418)
This commit is contained in:
parent
4d60b71583
commit
a2b3d7e30c
@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
@ -15,6 +16,7 @@ import (
|
|||||||
_ "github.com/breml/rootcerts"
|
_ "github.com/breml/rootcerts"
|
||||||
"github.com/qdm12/gluetun/internal/alpine"
|
"github.com/qdm12/gluetun/internal/alpine"
|
||||||
"github.com/qdm12/gluetun/internal/cli"
|
"github.com/qdm12/gluetun/internal/cli"
|
||||||
|
"github.com/qdm12/gluetun/internal/command"
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/gluetun/internal/configuration/sources/files"
|
"github.com/qdm12/gluetun/internal/configuration/sources/files"
|
||||||
"github.com/qdm12/gluetun/internal/configuration/sources/secrets"
|
"github.com/qdm12/gluetun/internal/configuration/sources/secrets"
|
||||||
@ -41,7 +43,6 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/updater/resolver"
|
"github.com/qdm12/gluetun/internal/updater/resolver"
|
||||||
"github.com/qdm12/gluetun/internal/updater/unzip"
|
"github.com/qdm12/gluetun/internal/updater/unzip"
|
||||||
"github.com/qdm12/gluetun/internal/vpn"
|
"github.com/qdm12/gluetun/internal/vpn"
|
||||||
"github.com/qdm12/golibs/command"
|
|
||||||
"github.com/qdm12/gosettings/reader"
|
"github.com/qdm12/gosettings/reader"
|
||||||
"github.com/qdm12/gosettings/reader/sources/env"
|
"github.com/qdm12/gosettings/reader/sources/env"
|
||||||
"github.com/qdm12/goshutdown"
|
"github.com/qdm12/goshutdown"
|
||||||
@ -78,7 +79,7 @@ func main() {
|
|||||||
netLinkDebugLogger := logger.New(log.SetComponent("netlink"))
|
netLinkDebugLogger := logger.New(log.SetComponent("netlink"))
|
||||||
netLinker := netlink.New(netLinkDebugLogger)
|
netLinker := netlink.New(netLinkDebugLogger)
|
||||||
cli := cli.New()
|
cli := cli.New()
|
||||||
cmder := command.NewCmder()
|
cmder := command.New()
|
||||||
|
|
||||||
reader := reader.New(reader.Settings{
|
reader := reader.New(reader.Settings{
|
||||||
Sources: []reader.Source{
|
Sources: []reader.Source{
|
||||||
@ -145,7 +146,7 @@ var (
|
|||||||
//nolint:gocognit,gocyclo,maintidx
|
//nolint:gocognit,gocyclo,maintidx
|
||||||
func _main(ctx context.Context, buildInfo models.BuildInformation,
|
func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||||
args []string, logger log.LoggerInterface, reader *reader.Reader,
|
args []string, logger log.LoggerInterface, reader *reader.Reader,
|
||||||
tun Tun, netLinker netLinker, cmder command.RunStarter,
|
tun Tun, netLinker netLinker, cmder RunStarter,
|
||||||
cli clier) error {
|
cli clier) error {
|
||||||
if len(args) > 1 { // cli operation
|
if len(args) > 1 { // cli operation
|
||||||
switch args[1] {
|
switch args[1] {
|
||||||
@ -591,3 +592,9 @@ type Tun interface {
|
|||||||
Check(tunDevice string) error
|
Check(tunDevice string) error
|
||||||
Create(tunDevice string) error
|
Create(tunDevice string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RunStarter interface {
|
||||||
|
Run(cmd *exec.Cmd) (output string, err error)
|
||||||
|
Start(cmd *exec.Cmd) (stdoutLines, stderrLines <-chan string,
|
||||||
|
waitError <-chan error, err error)
|
||||||
|
}
|
||||||
|
|||||||
8
internal/command/cmder.go
Normal file
8
internal/command/cmder.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
// Cmder handles running subprograms synchronously and asynchronously.
|
||||||
|
type Cmder struct{}
|
||||||
|
|
||||||
|
func New() *Cmder {
|
||||||
|
return &Cmder{}
|
||||||
|
}
|
||||||
11
internal/command/interfaces_local.go
Normal file
11
internal/command/interfaces_local.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
type execCmd interface {
|
||||||
|
CombinedOutput() ([]byte, error)
|
||||||
|
StdoutPipe() (io.ReadCloser, error)
|
||||||
|
StderrPipe() (io.ReadCloser, error)
|
||||||
|
Start() error
|
||||||
|
Wait() error
|
||||||
|
}
|
||||||
3
internal/command/mocks_generate_test.go
Normal file
3
internal/command/mocks_generate_test.go
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
//go:generate mockgen -destination=mocks_local_test.go -package=$GOPACKAGE -source=interfaces_local.go
|
||||||
108
internal/command/mocks_local_test.go
Normal file
108
internal/command/mocks_local_test.go
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: interfaces_local.go
|
||||||
|
|
||||||
|
// Package command is a generated GoMock package.
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
io "io"
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockexecCmd is a mock of execCmd interface.
|
||||||
|
type MockexecCmd struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockexecCmdMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockexecCmdMockRecorder is the mock recorder for MockexecCmd.
|
||||||
|
type MockexecCmdMockRecorder struct {
|
||||||
|
mock *MockexecCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockexecCmd creates a new mock instance.
|
||||||
|
func NewMockexecCmd(ctrl *gomock.Controller) *MockexecCmd {
|
||||||
|
mock := &MockexecCmd{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockexecCmdMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockexecCmd) EXPECT() *MockexecCmdMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// CombinedOutput mocks base method.
|
||||||
|
func (m *MockexecCmd) CombinedOutput() ([]byte, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "CombinedOutput")
|
||||||
|
ret0, _ := ret[0].([]byte)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// CombinedOutput indicates an expected call of CombinedOutput.
|
||||||
|
func (mr *MockexecCmdMockRecorder) CombinedOutput() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CombinedOutput", reflect.TypeOf((*MockexecCmd)(nil).CombinedOutput))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start mocks base method.
|
||||||
|
func (m *MockexecCmd) Start() error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Start")
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start indicates an expected call of Start.
|
||||||
|
func (mr *MockexecCmdMockRecorder) Start() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockexecCmd)(nil).Start))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StderrPipe mocks base method.
|
||||||
|
func (m *MockexecCmd) StderrPipe() (io.ReadCloser, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "StderrPipe")
|
||||||
|
ret0, _ := ret[0].(io.ReadCloser)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// StderrPipe indicates an expected call of StderrPipe.
|
||||||
|
func (mr *MockexecCmdMockRecorder) StderrPipe() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StderrPipe", reflect.TypeOf((*MockexecCmd)(nil).StderrPipe))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StdoutPipe mocks base method.
|
||||||
|
func (m *MockexecCmd) StdoutPipe() (io.ReadCloser, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "StdoutPipe")
|
||||||
|
ret0, _ := ret[0].(io.ReadCloser)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// StdoutPipe indicates an expected call of StdoutPipe.
|
||||||
|
func (mr *MockexecCmdMockRecorder) StdoutPipe() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StdoutPipe", reflect.TypeOf((*MockexecCmd)(nil).StdoutPipe))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait mocks base method.
|
||||||
|
func (m *MockexecCmd) Wait() error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Wait")
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait indicates an expected call of Wait.
|
||||||
|
func (mr *MockexecCmdMockRecorder) Wait() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Wait", reflect.TypeOf((*MockexecCmd)(nil).Wait))
|
||||||
|
}
|
||||||
30
internal/command/run.go
Normal file
30
internal/command/run.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Run runs a command in a blocking manner, returning its output and
|
||||||
|
// an error if it failed.
|
||||||
|
func (c *Cmder) Run(cmd *exec.Cmd) (output string, err error) {
|
||||||
|
return run(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(cmd execCmd) (output string, err error) {
|
||||||
|
stdout, err := cmd.CombinedOutput()
|
||||||
|
output = string(stdout)
|
||||||
|
output = strings.TrimSuffix(output, "\n")
|
||||||
|
lines := stringToLines(output)
|
||||||
|
for i := range lines {
|
||||||
|
lines[i] = strings.TrimPrefix(lines[i], "'")
|
||||||
|
lines[i] = strings.TrimSuffix(lines[i], "'")
|
||||||
|
}
|
||||||
|
output = strings.Join(lines, "\n")
|
||||||
|
return output, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringToLines(s string) (lines []string) {
|
||||||
|
s = strings.TrimSuffix(s, "\n")
|
||||||
|
return strings.Split(s, "\n")
|
||||||
|
}
|
||||||
55
internal/command/run_test.go
Normal file
55
internal/command/run_test.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_run(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
errDummy := errors.New("dummy")
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
stdout []byte
|
||||||
|
cmdErr error
|
||||||
|
output string
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
"no output": {},
|
||||||
|
"cmd error": {
|
||||||
|
stdout: []byte("'hello \nworld'\n"),
|
||||||
|
cmdErr: errDummy,
|
||||||
|
output: "hello \nworld",
|
||||||
|
err: errDummy,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
|
||||||
|
mockCmd := NewMockexecCmd(ctrl)
|
||||||
|
|
||||||
|
mockCmd.EXPECT().CombinedOutput().Return(testCase.stdout, testCase.cmdErr)
|
||||||
|
|
||||||
|
output, err := run(mockCmd)
|
||||||
|
|
||||||
|
if testCase.err != nil {
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Equal(t, testCase.err.Error(), err.Error())
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, testCase.output, output)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
97
internal/command/start.go
Normal file
97
internal/command/start.go
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Start launches a command and streams stdout and stderr to channels.
|
||||||
|
// All the channels returned are ready only and won't be closed
|
||||||
|
// if the command fails later.
|
||||||
|
func (c *Cmder) Start(cmd *exec.Cmd) (
|
||||||
|
stdoutLines, stderrLines <-chan string,
|
||||||
|
waitError <-chan error, startErr error) {
|
||||||
|
return start(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func start(cmd execCmd) (stdoutLines, stderrLines <-chan string,
|
||||||
|
waitError <-chan error, startErr error) {
|
||||||
|
stop := make(chan struct{})
|
||||||
|
stdoutReady := make(chan struct{})
|
||||||
|
stdoutLinesCh := make(chan string)
|
||||||
|
stdoutDone := make(chan struct{})
|
||||||
|
stderrReady := make(chan struct{})
|
||||||
|
stderrLinesCh := make(chan string)
|
||||||
|
stderrDone := make(chan struct{})
|
||||||
|
|
||||||
|
stdout, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
go streamToChannel(stdoutReady, stop, stdoutDone, stdout, stdoutLinesCh)
|
||||||
|
|
||||||
|
stderr, err := cmd.StderrPipe()
|
||||||
|
if err != nil {
|
||||||
|
_ = stdout.Close()
|
||||||
|
close(stop)
|
||||||
|
<-stdoutDone
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
go streamToChannel(stderrReady, stop, stderrDone, stderr, stderrLinesCh)
|
||||||
|
|
||||||
|
err = cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
_ = stdout.Close()
|
||||||
|
_ = stderr.Close()
|
||||||
|
close(stop)
|
||||||
|
<-stdoutDone
|
||||||
|
<-stderrDone
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
waitErrorCh := make(chan error)
|
||||||
|
go func() {
|
||||||
|
err := cmd.Wait()
|
||||||
|
_ = stdout.Close()
|
||||||
|
_ = stderr.Close()
|
||||||
|
close(stop)
|
||||||
|
<-stdoutDone
|
||||||
|
<-stderrDone
|
||||||
|
waitErrorCh <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
return stdoutLinesCh, stderrLinesCh, waitErrorCh, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func streamToChannel(ready chan<- struct{},
|
||||||
|
stop <-chan struct{}, done chan<- struct{},
|
||||||
|
stream io.Reader, lines chan<- string) {
|
||||||
|
defer close(done)
|
||||||
|
close(ready)
|
||||||
|
scanner := bufio.NewScanner(stream)
|
||||||
|
lineBuffer := make([]byte, bufio.MaxScanTokenSize) // 64KB
|
||||||
|
const maxCapacity = 20 * 1024 * 1024 // 20MB
|
||||||
|
scanner.Buffer(lineBuffer, maxCapacity)
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
// scanner is closed if the context is canceled
|
||||||
|
// or if the command failed starting because the
|
||||||
|
// stream is closed (io.EOF error).
|
||||||
|
lines <- scanner.Text()
|
||||||
|
}
|
||||||
|
err := scanner.Err()
|
||||||
|
if err == nil || errors.Is(err, os.ErrClosed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore the error if it is stopped.
|
||||||
|
select {
|
||||||
|
case <-stop:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
lines <- "stream error: " + err.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
119
internal/command/start_test.go
Normal file
119
internal/command/start_test.go
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func linesToReadCloser(lines []string) io.ReadCloser {
|
||||||
|
s := strings.Join(lines, "\n")
|
||||||
|
return io.NopCloser(bytes.NewBufferString(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_start(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
errDummy := errors.New("dummy")
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
stdout []string
|
||||||
|
stdoutPipeErr error
|
||||||
|
stderr []string
|
||||||
|
stderrPipeErr error
|
||||||
|
startErr error
|
||||||
|
waitErr error
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
"no output": {},
|
||||||
|
"success": {
|
||||||
|
stdout: []string{"hello", "world"},
|
||||||
|
stderr: []string{"some", "error"},
|
||||||
|
},
|
||||||
|
"stdout pipe error": {
|
||||||
|
stdoutPipeErr: errDummy,
|
||||||
|
err: errDummy,
|
||||||
|
},
|
||||||
|
"stderr pipe error": {
|
||||||
|
stderrPipeErr: errDummy,
|
||||||
|
err: errDummy,
|
||||||
|
},
|
||||||
|
"start error": {
|
||||||
|
startErr: errDummy,
|
||||||
|
err: errDummy,
|
||||||
|
},
|
||||||
|
"wait error": {
|
||||||
|
waitErr: errDummy,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
|
||||||
|
stdout := linesToReadCloser(testCase.stdout)
|
||||||
|
stderr := linesToReadCloser(testCase.stderr)
|
||||||
|
|
||||||
|
mockCmd := NewMockexecCmd(ctrl)
|
||||||
|
|
||||||
|
mockCmd.EXPECT().StdoutPipe().
|
||||||
|
Return(stdout, testCase.stdoutPipeErr)
|
||||||
|
if testCase.stdoutPipeErr == nil {
|
||||||
|
mockCmd.EXPECT().StderrPipe().Return(stderr, testCase.stderrPipeErr)
|
||||||
|
if testCase.stderrPipeErr == nil {
|
||||||
|
mockCmd.EXPECT().Start().Return(testCase.startErr)
|
||||||
|
if testCase.startErr == nil {
|
||||||
|
mockCmd.EXPECT().Wait().Return(testCase.waitErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stdoutLines, stderrLines, waitError, err := start(mockCmd)
|
||||||
|
|
||||||
|
if testCase.err != nil {
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Equal(t, testCase.err.Error(), err.Error())
|
||||||
|
assert.Nil(t, stdoutLines)
|
||||||
|
assert.Nil(t, stderrLines)
|
||||||
|
assert.Nil(t, waitError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var stdoutIndex, stderrIndex int
|
||||||
|
|
||||||
|
done := false
|
||||||
|
for !done {
|
||||||
|
select {
|
||||||
|
case line := <-stdoutLines:
|
||||||
|
assert.Equal(t, testCase.stdout[stdoutIndex], line)
|
||||||
|
stdoutIndex++
|
||||||
|
case line := <-stderrLines:
|
||||||
|
assert.Equal(t, testCase.stderr[stderrIndex], line)
|
||||||
|
stderrIndex++
|
||||||
|
case err := <-waitError:
|
||||||
|
if testCase.waitErr != nil {
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Equal(t, testCase.waitErr.Error(), err.Error())
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
done = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, len(testCase.stdout), stdoutIndex)
|
||||||
|
assert.Equal(t, len(testCase.stderr), stderrIndex)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -33,7 +33,7 @@ func isDeleteMatchInstruction(instruction string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func deleteIPTablesRule(ctx context.Context, iptablesBinary, instruction string,
|
func deleteIPTablesRule(ctx context.Context, iptablesBinary, instruction string,
|
||||||
runner Runner, logger Logger) (err error) {
|
runner CmdRunner, logger Logger) (err error) {
|
||||||
targetRule, err := parseIptablesInstruction(instruction)
|
targetRule, err := parseIptablesInstruction(instruction)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("parsing iptables command: %w", err)
|
return fmt.Errorf("parsing iptables command: %w", err)
|
||||||
@ -68,7 +68,7 @@ func deleteIPTablesRule(ctx context.Context, iptablesBinary, instruction string,
|
|||||||
// findLineNumber finds the line number of an iptables rule.
|
// findLineNumber finds the line number of an iptables rule.
|
||||||
// It returns 0 if the rule is not found.
|
// It returns 0 if the rule is not found.
|
||||||
func findLineNumber(ctx context.Context, iptablesBinary string,
|
func findLineNumber(ctx context.Context, iptablesBinary string,
|
||||||
instruction iptablesInstruction, runner Runner, logger Logger) (
|
instruction iptablesInstruction, runner CmdRunner, logger Logger) (
|
||||||
lineNumber uint16, err error) {
|
lineNumber uint16, err error) {
|
||||||
listFlags := []string{"-t", instruction.table, "-L", instruction.chain,
|
listFlags := []string{"-t", instruction.table, "-L", instruction.chain,
|
||||||
"--line-numbers", "-n", "-v"}
|
"--line-numbers", "-n", "-v"}
|
||||||
|
|||||||
@ -62,7 +62,7 @@ func Test_deleteIPTablesRule(t *testing.T) {
|
|||||||
|
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
instruction string
|
instruction string
|
||||||
makeRunner func(ctrl *gomock.Controller) *MockRunner
|
makeRunner func(ctrl *gomock.Controller) *MockCmdRunner
|
||||||
makeLogger func(ctrl *gomock.Controller) *MockLogger
|
makeLogger func(ctrl *gomock.Controller) *MockLogger
|
||||||
errWrapped error
|
errWrapped error
|
||||||
errMessage string
|
errMessage string
|
||||||
@ -75,8 +75,8 @@ func Test_deleteIPTablesRule(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"list_error": {
|
"list_error": {
|
||||||
instruction: "-t nat --delete PREROUTING -i tun0 -p tcp --dport 43716 -j REDIRECT --to-ports 5678",
|
instruction: "-t nat --delete PREROUTING -i tun0 -p tcp --dport 43716 -j REDIRECT --to-ports 5678",
|
||||||
makeRunner: func(ctrl *gomock.Controller) *MockRunner {
|
makeRunner: func(ctrl *gomock.Controller) *MockCmdRunner {
|
||||||
runner := NewMockRunner(ctrl)
|
runner := NewMockCmdRunner(ctrl)
|
||||||
runner.EXPECT().
|
runner.EXPECT().
|
||||||
Run(newCmdMatcherListRules(iptablesBinary, "nat", "PREROUTING")).
|
Run(newCmdMatcherListRules(iptablesBinary, "nat", "PREROUTING")).
|
||||||
Return("", errTest)
|
Return("", errTest)
|
||||||
@ -93,8 +93,8 @@ func Test_deleteIPTablesRule(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"rule_not_found": {
|
"rule_not_found": {
|
||||||
instruction: "-t nat --delete PREROUTING -i tun0 -p tcp --dport 43716 -j REDIRECT --to-ports 5678",
|
instruction: "-t nat --delete PREROUTING -i tun0 -p tcp --dport 43716 -j REDIRECT --to-ports 5678",
|
||||||
makeRunner: func(ctrl *gomock.Controller) *MockRunner {
|
makeRunner: func(ctrl *gomock.Controller) *MockCmdRunner {
|
||||||
runner := NewMockRunner(ctrl)
|
runner := NewMockCmdRunner(ctrl)
|
||||||
runner.EXPECT().Run(newCmdMatcherListRules(iptablesBinary, "nat", "PREROUTING")).
|
runner.EXPECT().Run(newCmdMatcherListRules(iptablesBinary, "nat", "PREROUTING")).
|
||||||
Return(`Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
|
Return(`Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
|
||||||
num pkts bytes target prot opt in out source destination
|
num pkts bytes target prot opt in out source destination
|
||||||
@ -112,8 +112,8 @@ func Test_deleteIPTablesRule(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"rule_found_delete_error": {
|
"rule_found_delete_error": {
|
||||||
instruction: "-t nat --delete PREROUTING -i tun0 -p tcp --dport 43716 -j REDIRECT --to-ports 5678",
|
instruction: "-t nat --delete PREROUTING -i tun0 -p tcp --dport 43716 -j REDIRECT --to-ports 5678",
|
||||||
makeRunner: func(ctrl *gomock.Controller) *MockRunner {
|
makeRunner: func(ctrl *gomock.Controller) *MockCmdRunner {
|
||||||
runner := NewMockRunner(ctrl)
|
runner := NewMockCmdRunner(ctrl)
|
||||||
runner.EXPECT().Run(newCmdMatcherListRules(iptablesBinary, "nat", "PREROUTING")).
|
runner.EXPECT().Run(newCmdMatcherListRules(iptablesBinary, "nat", "PREROUTING")).
|
||||||
Return("Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)\n"+
|
Return("Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)\n"+
|
||||||
"num pkts bytes target prot opt in out source destination \n"+
|
"num pkts bytes target prot opt in out source destination \n"+
|
||||||
@ -137,8 +137,8 @@ func Test_deleteIPTablesRule(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"rule_found_delete_success": {
|
"rule_found_delete_success": {
|
||||||
instruction: "-t nat --delete PREROUTING -i tun0 -p tcp --dport 43716 -j REDIRECT --to-ports 5678",
|
instruction: "-t nat --delete PREROUTING -i tun0 -p tcp --dport 43716 -j REDIRECT --to-ports 5678",
|
||||||
makeRunner: func(ctrl *gomock.Controller) *MockRunner {
|
makeRunner: func(ctrl *gomock.Controller) *MockCmdRunner {
|
||||||
runner := NewMockRunner(ctrl)
|
runner := NewMockCmdRunner(ctrl)
|
||||||
runner.EXPECT().Run(newCmdMatcherListRules(iptablesBinary, "nat", "PREROUTING")).
|
runner.EXPECT().Run(newCmdMatcherListRules(iptablesBinary, "nat", "PREROUTING")).
|
||||||
Return("Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)\n"+
|
Return("Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)\n"+
|
||||||
"num pkts bytes target prot opt in out source destination \n"+
|
"num pkts bytes target prot opt in out source destination \n"+
|
||||||
@ -168,7 +168,7 @@ func Test_deleteIPTablesRule(t *testing.T) {
|
|||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
instruction := testCase.instruction
|
instruction := testCase.instruction
|
||||||
var runner *MockRunner
|
var runner *MockCmdRunner
|
||||||
if testCase.makeRunner != nil {
|
if testCase.makeRunner != nil {
|
||||||
runner = testCase.makeRunner(ctrl)
|
runner = testCase.makeRunner(ctrl)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,11 +7,10 @@ import (
|
|||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/gluetun/internal/routing"
|
"github.com/qdm12/gluetun/internal/routing"
|
||||||
"github.com/qdm12/golibs/command"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct { //nolint:maligned
|
type Config struct { //nolint:maligned
|
||||||
runner command.Runner
|
runner CmdRunner
|
||||||
logger Logger
|
logger Logger
|
||||||
iptablesMutex sync.Mutex
|
iptablesMutex sync.Mutex
|
||||||
ip6tablesMutex sync.Mutex
|
ip6tablesMutex sync.Mutex
|
||||||
@ -36,7 +35,7 @@ type Config struct { //nolint:maligned
|
|||||||
// NewConfig creates a new Config instance and returns an error
|
// NewConfig creates a new Config instance and returns an error
|
||||||
// if no iptables implementation is available.
|
// if no iptables implementation is available.
|
||||||
func NewConfig(ctx context.Context, logger Logger,
|
func NewConfig(ctx context.Context, logger Logger,
|
||||||
runner command.Runner, defaultRoutes []routing.DefaultRoute,
|
runner CmdRunner, defaultRoutes []routing.DefaultRoute,
|
||||||
localNetworks []routing.LocalNetwork) (config *Config, err error) {
|
localNetworks []routing.LocalNetwork) (config *Config, err error) {
|
||||||
iptables, err := checkIptablesSupport(ctx, runner, "iptables", "iptables-nft", "iptables-legacy")
|
iptables, err := checkIptablesSupport(ctx, runner, "iptables", "iptables-nft", "iptables-legacy")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
package firewall
|
package firewall
|
||||||
|
|
||||||
import "github.com/qdm12/golibs/command"
|
import "os/exec"
|
||||||
|
|
||||||
type Runner interface {
|
type CmdRunner interface {
|
||||||
Run(cmd command.ExecCmd) (output string, err error)
|
Run(cmd *exec.Cmd) (output string, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Logger interface {
|
type Logger interface {
|
||||||
|
|||||||
@ -6,14 +6,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/golibs/command"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// findIP6tablesSupported checks for multiple iptables implementations
|
// findIP6tablesSupported checks for multiple iptables implementations
|
||||||
// and returns the iptables path that is supported. If none work, an
|
// and returns the iptables path that is supported. If none work, an
|
||||||
// empty string path is returned.
|
// empty string path is returned.
|
||||||
func findIP6tablesSupported(ctx context.Context, runner command.Runner) (
|
func findIP6tablesSupported(ctx context.Context, runner CmdRunner) (
|
||||||
ip6tablesPath string, err error) {
|
ip6tablesPath string, err error) {
|
||||||
ip6tablesPath, err = checkIptablesSupport(ctx, runner, "ip6tables", "ip6tables-nft", "ip6tables-legacy")
|
ip6tablesPath, err = checkIptablesSupport(ctx, runner, "ip6tables", "ip6tables-nft", "ip6tables-legacy")
|
||||||
if errors.Is(err, ErrIPTablesNotSupported) {
|
if errors.Is(err, ErrIPTablesNotSupported) {
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
package firewall
|
package firewall
|
||||||
|
|
||||||
//go:generate mockgen -destination=mocks_test.go -package=$GOPACKAGE . Runner,Logger
|
//go:generate mockgen -destination=mocks_test.go -package $GOPACKAGE . CmdRunner,Logger
|
||||||
|
|||||||
@ -1,41 +1,41 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
// Source: github.com/qdm12/gluetun/internal/firewall (interfaces: Runner,Logger)
|
// Source: github.com/qdm12/gluetun/internal/firewall (interfaces: CmdRunner,Logger)
|
||||||
|
|
||||||
// Package firewall is a generated GoMock package.
|
// Package firewall is a generated GoMock package.
|
||||||
package firewall
|
package firewall
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
exec "os/exec"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
command "github.com/qdm12/golibs/command"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockRunner is a mock of Runner interface.
|
// MockCmdRunner is a mock of CmdRunner interface.
|
||||||
type MockRunner struct {
|
type MockCmdRunner struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
recorder *MockRunnerMockRecorder
|
recorder *MockCmdRunnerMockRecorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockRunnerMockRecorder is the mock recorder for MockRunner.
|
// MockCmdRunnerMockRecorder is the mock recorder for MockCmdRunner.
|
||||||
type MockRunnerMockRecorder struct {
|
type MockCmdRunnerMockRecorder struct {
|
||||||
mock *MockRunner
|
mock *MockCmdRunner
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMockRunner creates a new mock instance.
|
// NewMockCmdRunner creates a new mock instance.
|
||||||
func NewMockRunner(ctrl *gomock.Controller) *MockRunner {
|
func NewMockCmdRunner(ctrl *gomock.Controller) *MockCmdRunner {
|
||||||
mock := &MockRunner{ctrl: ctrl}
|
mock := &MockCmdRunner{ctrl: ctrl}
|
||||||
mock.recorder = &MockRunnerMockRecorder{mock}
|
mock.recorder = &MockCmdRunnerMockRecorder{mock}
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
func (m *MockRunner) EXPECT() *MockRunnerMockRecorder {
|
func (m *MockCmdRunner) EXPECT() *MockCmdRunnerMockRecorder {
|
||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run mocks base method.
|
// Run mocks base method.
|
||||||
func (m *MockRunner) Run(arg0 command.ExecCmd) (string, error) {
|
func (m *MockCmdRunner) Run(arg0 *exec.Cmd) (string, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Run", arg0)
|
ret := m.ctrl.Call(m, "Run", arg0)
|
||||||
ret0, _ := ret[0].(string)
|
ret0, _ := ret[0].(string)
|
||||||
@ -44,9 +44,9 @@ func (m *MockRunner) Run(arg0 command.ExecCmd) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run indicates an expected call of Run.
|
// Run indicates an expected call of Run.
|
||||||
func (mr *MockRunnerMockRecorder) Run(arg0 interface{}) *gomock.Call {
|
func (mr *MockCmdRunnerMockRecorder) Run(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockRunner)(nil).Run), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockCmdRunner)(nil).Run), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockLogger is a mock of Logger interface.
|
// MockLogger is a mock of Logger interface.
|
||||||
|
|||||||
@ -8,8 +8,6 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/golibs/command"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -19,7 +17,7 @@ var (
|
|||||||
ErrIPTablesNotSupported = errors.New("no iptables supported found")
|
ErrIPTablesNotSupported = errors.New("no iptables supported found")
|
||||||
)
|
)
|
||||||
|
|
||||||
func checkIptablesSupport(ctx context.Context, runner command.Runner,
|
func checkIptablesSupport(ctx context.Context, runner CmdRunner,
|
||||||
iptablesPathsToTry ...string) (iptablesPath string, err error) {
|
iptablesPathsToTry ...string) (iptablesPath string, err error) {
|
||||||
iptablesPathToUnsupportedMessage := make(map[string]string, len(iptablesPathsToTry))
|
iptablesPathToUnsupportedMessage := make(map[string]string, len(iptablesPathsToTry))
|
||||||
for _, pathToTest := range iptablesPathsToTry {
|
for _, pathToTest := range iptablesPathsToTry {
|
||||||
@ -62,7 +60,7 @@ func checkIptablesSupport(ctx context.Context, runner command.Runner,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testIptablesPath(ctx context.Context, path string,
|
func testIptablesPath(ctx context.Context, path string,
|
||||||
runner command.Runner) (ok bool, unsupportedMessage string,
|
runner CmdRunner) (ok bool, unsupportedMessage string,
|
||||||
criticalErr error) {
|
criticalErr error) {
|
||||||
// Just listing iptables rules often work but we need
|
// Just listing iptables rules often work but we need
|
||||||
// to modify them to ensure we can support the iptables
|
// to modify them to ensure we can support the iptables
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/qdm12/golibs/command"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@ -41,15 +40,15 @@ func Test_checkIptablesSupport(t *testing.T) {
|
|||||||
const inputPolicy = "ACCEPT"
|
const inputPolicy = "ACCEPT"
|
||||||
|
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
buildRunner func(ctrl *gomock.Controller) command.Runner
|
buildRunner func(ctrl *gomock.Controller) CmdRunner
|
||||||
iptablesPathsToTry []string
|
iptablesPathsToTry []string
|
||||||
iptablesPath string
|
iptablesPath string
|
||||||
errSentinel error
|
errSentinel error
|
||||||
errMessage string
|
errMessage string
|
||||||
}{
|
}{
|
||||||
"critical error when checking": {
|
"critical error when checking": {
|
||||||
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
buildRunner: func(ctrl *gomock.Controller) CmdRunner {
|
||||||
runner := NewMockRunner(ctrl)
|
runner := NewMockCmdRunner(ctrl)
|
||||||
runner.EXPECT().Run(newAppendTestRuleMatcher("path1")).
|
runner.EXPECT().Run(newAppendTestRuleMatcher("path1")).
|
||||||
Return("", nil)
|
Return("", nil)
|
||||||
runner.EXPECT().Run(newDeleteTestRuleMatcher("path1")).
|
runner.EXPECT().Run(newDeleteTestRuleMatcher("path1")).
|
||||||
@ -62,8 +61,8 @@ func Test_checkIptablesSupport(t *testing.T) {
|
|||||||
"output (exit code 4)",
|
"output (exit code 4)",
|
||||||
},
|
},
|
||||||
"found valid path": {
|
"found valid path": {
|
||||||
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
buildRunner: func(ctrl *gomock.Controller) CmdRunner {
|
||||||
runner := NewMockRunner(ctrl)
|
runner := NewMockCmdRunner(ctrl)
|
||||||
runner.EXPECT().Run(newAppendTestRuleMatcher("path1")).
|
runner.EXPECT().Run(newAppendTestRuleMatcher("path1")).
|
||||||
Return("", nil)
|
Return("", nil)
|
||||||
runner.EXPECT().Run(newDeleteTestRuleMatcher("path1")).
|
runner.EXPECT().Run(newDeleteTestRuleMatcher("path1")).
|
||||||
@ -78,8 +77,8 @@ func Test_checkIptablesSupport(t *testing.T) {
|
|||||||
iptablesPath: "path1",
|
iptablesPath: "path1",
|
||||||
},
|
},
|
||||||
"all permission denied": {
|
"all permission denied": {
|
||||||
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
buildRunner: func(ctrl *gomock.Controller) CmdRunner {
|
||||||
runner := NewMockRunner(ctrl)
|
runner := NewMockCmdRunner(ctrl)
|
||||||
runner.EXPECT().Run(newAppendTestRuleMatcher("path1")).
|
runner.EXPECT().Run(newAppendTestRuleMatcher("path1")).
|
||||||
Return("Permission denied (you must be root) more context", errDummy)
|
Return("Permission denied (you must be root) more context", errDummy)
|
||||||
runner.EXPECT().Run(newAppendTestRuleMatcher("path2")).
|
runner.EXPECT().Run(newAppendTestRuleMatcher("path2")).
|
||||||
@ -93,8 +92,8 @@ func Test_checkIptablesSupport(t *testing.T) {
|
|||||||
"path2: context: Permission denied (you must be root) (exit code 4)",
|
"path2: context: Permission denied (you must be root) (exit code 4)",
|
||||||
},
|
},
|
||||||
"no valid path": {
|
"no valid path": {
|
||||||
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
buildRunner: func(ctrl *gomock.Controller) CmdRunner {
|
||||||
runner := NewMockRunner(ctrl)
|
runner := NewMockCmdRunner(ctrl)
|
||||||
runner.EXPECT().Run(newAppendTestRuleMatcher("path1")).
|
runner.EXPECT().Run(newAppendTestRuleMatcher("path1")).
|
||||||
Return("output 1", errDummy)
|
Return("output 1", errDummy)
|
||||||
runner.EXPECT().Run(newAppendTestRuleMatcher("path2")).
|
runner.EXPECT().Run(newAppendTestRuleMatcher("path2")).
|
||||||
@ -139,15 +138,15 @@ func Test_testIptablesPath(t *testing.T) {
|
|||||||
const inputPolicy = "ACCEPT"
|
const inputPolicy = "ACCEPT"
|
||||||
|
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
buildRunner func(ctrl *gomock.Controller) command.Runner
|
buildRunner func(ctrl *gomock.Controller) CmdRunner
|
||||||
ok bool
|
ok bool
|
||||||
unsupportedMessage string
|
unsupportedMessage string
|
||||||
criticalErrWrapped error
|
criticalErrWrapped error
|
||||||
criticalErrMessage string
|
criticalErrMessage string
|
||||||
}{
|
}{
|
||||||
"append test rule permission denied": {
|
"append test rule permission denied": {
|
||||||
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
buildRunner: func(ctrl *gomock.Controller) CmdRunner {
|
||||||
runner := NewMockRunner(ctrl)
|
runner := NewMockCmdRunner(ctrl)
|
||||||
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).
|
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).
|
||||||
Return("Permission denied (you must be root)", errDummy)
|
Return("Permission denied (you must be root)", errDummy)
|
||||||
return runner
|
return runner
|
||||||
@ -155,8 +154,8 @@ func Test_testIptablesPath(t *testing.T) {
|
|||||||
unsupportedMessage: "Permission denied (you must be root) (exit code 4)",
|
unsupportedMessage: "Permission denied (you must be root) (exit code 4)",
|
||||||
},
|
},
|
||||||
"append test rule unsupported": {
|
"append test rule unsupported": {
|
||||||
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
buildRunner: func(ctrl *gomock.Controller) CmdRunner {
|
||||||
runner := NewMockRunner(ctrl)
|
runner := NewMockCmdRunner(ctrl)
|
||||||
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).
|
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).
|
||||||
Return("some output", errDummy)
|
Return("some output", errDummy)
|
||||||
return runner
|
return runner
|
||||||
@ -164,8 +163,8 @@ func Test_testIptablesPath(t *testing.T) {
|
|||||||
unsupportedMessage: "some output (exit code 4)",
|
unsupportedMessage: "some output (exit code 4)",
|
||||||
},
|
},
|
||||||
"remove test rule error": {
|
"remove test rule error": {
|
||||||
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
buildRunner: func(ctrl *gomock.Controller) CmdRunner {
|
||||||
runner := NewMockRunner(ctrl)
|
runner := NewMockCmdRunner(ctrl)
|
||||||
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
|
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
|
||||||
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).
|
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).
|
||||||
Return("some output", errDummy)
|
Return("some output", errDummy)
|
||||||
@ -175,8 +174,8 @@ func Test_testIptablesPath(t *testing.T) {
|
|||||||
criticalErrMessage: "failed cleaning up test rule: some output (exit code 4)",
|
criticalErrMessage: "failed cleaning up test rule: some output (exit code 4)",
|
||||||
},
|
},
|
||||||
"list input rules permission denied": {
|
"list input rules permission denied": {
|
||||||
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
buildRunner: func(ctrl *gomock.Controller) CmdRunner {
|
||||||
runner := NewMockRunner(ctrl)
|
runner := NewMockCmdRunner(ctrl)
|
||||||
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
|
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
|
||||||
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).Return("", nil)
|
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).Return("", nil)
|
||||||
runner.EXPECT().Run(newListInputRulesMatcher(path)).
|
runner.EXPECT().Run(newListInputRulesMatcher(path)).
|
||||||
@ -186,8 +185,8 @@ func Test_testIptablesPath(t *testing.T) {
|
|||||||
unsupportedMessage: "Permission denied (you must be root) (exit code 4)",
|
unsupportedMessage: "Permission denied (you must be root) (exit code 4)",
|
||||||
},
|
},
|
||||||
"list input rules unsupported": {
|
"list input rules unsupported": {
|
||||||
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
buildRunner: func(ctrl *gomock.Controller) CmdRunner {
|
||||||
runner := NewMockRunner(ctrl)
|
runner := NewMockCmdRunner(ctrl)
|
||||||
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
|
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
|
||||||
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).Return("", nil)
|
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).Return("", nil)
|
||||||
runner.EXPECT().Run(newListInputRulesMatcher(path)).
|
runner.EXPECT().Run(newListInputRulesMatcher(path)).
|
||||||
@ -197,8 +196,8 @@ func Test_testIptablesPath(t *testing.T) {
|
|||||||
unsupportedMessage: "some output (exit code 4)",
|
unsupportedMessage: "some output (exit code 4)",
|
||||||
},
|
},
|
||||||
"list input rules no policy": {
|
"list input rules no policy": {
|
||||||
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
buildRunner: func(ctrl *gomock.Controller) CmdRunner {
|
||||||
runner := NewMockRunner(ctrl)
|
runner := NewMockCmdRunner(ctrl)
|
||||||
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
|
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
|
||||||
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).Return("", nil)
|
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).Return("", nil)
|
||||||
runner.EXPECT().Run(newListInputRulesMatcher(path)).
|
runner.EXPECT().Run(newListInputRulesMatcher(path)).
|
||||||
@ -209,8 +208,8 @@ func Test_testIptablesPath(t *testing.T) {
|
|||||||
criticalErrMessage: "input policy not found: in INPUT rules: some\noutput",
|
criticalErrMessage: "input policy not found: in INPUT rules: some\noutput",
|
||||||
},
|
},
|
||||||
"set policy permission denied": {
|
"set policy permission denied": {
|
||||||
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
buildRunner: func(ctrl *gomock.Controller) CmdRunner {
|
||||||
runner := NewMockRunner(ctrl)
|
runner := NewMockCmdRunner(ctrl)
|
||||||
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
|
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
|
||||||
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).Return("", nil)
|
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).Return("", nil)
|
||||||
runner.EXPECT().Run(newListInputRulesMatcher(path)).
|
runner.EXPECT().Run(newListInputRulesMatcher(path)).
|
||||||
@ -222,8 +221,8 @@ func Test_testIptablesPath(t *testing.T) {
|
|||||||
unsupportedMessage: "Permission denied (you must be root) (exit code 4)",
|
unsupportedMessage: "Permission denied (you must be root) (exit code 4)",
|
||||||
},
|
},
|
||||||
"set policy unsupported": {
|
"set policy unsupported": {
|
||||||
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
buildRunner: func(ctrl *gomock.Controller) CmdRunner {
|
||||||
runner := NewMockRunner(ctrl)
|
runner := NewMockCmdRunner(ctrl)
|
||||||
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
|
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
|
||||||
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).Return("", nil)
|
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).Return("", nil)
|
||||||
runner.EXPECT().Run(newListInputRulesMatcher(path)).
|
runner.EXPECT().Run(newListInputRulesMatcher(path)).
|
||||||
@ -235,8 +234,8 @@ func Test_testIptablesPath(t *testing.T) {
|
|||||||
unsupportedMessage: "some output (exit code 4)",
|
unsupportedMessage: "some output (exit code 4)",
|
||||||
},
|
},
|
||||||
"success": {
|
"success": {
|
||||||
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
buildRunner: func(ctrl *gomock.Controller) CmdRunner {
|
||||||
runner := NewMockRunner(ctrl)
|
runner := NewMockCmdRunner(ctrl)
|
||||||
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
|
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
|
||||||
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).Return("", nil)
|
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).Return("", nil)
|
||||||
runner.EXPECT().Run(newListInputRulesMatcher(path)).
|
runner.EXPECT().Run(newListInputRulesMatcher(path)).
|
||||||
|
|||||||
14
internal/openvpn/interfaces.go
Normal file
14
internal/openvpn/interfaces.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package openvpn
|
||||||
|
|
||||||
|
import "os/exec"
|
||||||
|
|
||||||
|
type CmdStarter interface {
|
||||||
|
Start(cmd *exec.Cmd) (
|
||||||
|
stdoutLines, stderrLines <-chan string,
|
||||||
|
waitError <-chan error, startErr error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type CmdRunStarter interface {
|
||||||
|
Run(cmd *exec.Cmd) (output string, err error)
|
||||||
|
CmdStarter
|
||||||
|
}
|
||||||
@ -2,19 +2,18 @@ package openvpn
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/qdm12/gluetun/internal/constants/openvpn"
|
"github.com/qdm12/gluetun/internal/constants/openvpn"
|
||||||
"github.com/qdm12/golibs/command"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Configurator struct {
|
type Configurator struct {
|
||||||
logger Infoer
|
logger Infoer
|
||||||
cmder command.RunStarter
|
cmder CmdRunStarter
|
||||||
configPath string
|
configPath string
|
||||||
authFilePath string
|
authFilePath string
|
||||||
askPassPath string
|
askPassPath string
|
||||||
puid, pgid int
|
puid, pgid int
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(logger Infoer, cmder command.RunStarter,
|
func New(logger Infoer, cmder CmdRunStarter,
|
||||||
puid, pgid int) *Configurator {
|
puid, pgid int) *Configurator {
|
||||||
return &Configurator{
|
return &Configurator{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
|||||||
@ -4,16 +4,15 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/golibs/command"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Runner struct {
|
type Runner struct {
|
||||||
settings settings.OpenVPN
|
settings settings.OpenVPN
|
||||||
starter command.Starter
|
starter CmdStarter
|
||||||
logger Logger
|
logger Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRunner(settings settings.OpenVPN, starter command.Starter,
|
func NewRunner(settings settings.OpenVPN, starter CmdStarter,
|
||||||
logger Logger) *Runner {
|
logger Logger) *Runner {
|
||||||
return &Runner{
|
return &Runner{
|
||||||
starter: starter,
|
starter: starter,
|
||||||
@ -37,12 +36,10 @@ func (r *Runner) Run(ctx context.Context, errCh chan<- error, ready chan<- struc
|
|||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
<-waitError
|
<-waitError
|
||||||
close(waitError)
|
|
||||||
streamCancel()
|
streamCancel()
|
||||||
<-streamDone
|
<-streamDone
|
||||||
errCh <- ctx.Err()
|
errCh <- ctx.Err()
|
||||||
case err := <-waitError:
|
case err := <-waitError:
|
||||||
close(waitError)
|
|
||||||
streamCancel()
|
streamCancel()
|
||||||
<-streamDone
|
<-streamDone
|
||||||
errCh <- err
|
errCh <- err
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants/openvpn"
|
"github.com/qdm12/gluetun/internal/constants/openvpn"
|
||||||
"github.com/qdm12/golibs/command"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrVersionUnknown = errors.New("OpenVPN version is unknown")
|
var ErrVersionUnknown = errors.New("OpenVPN version is unknown")
|
||||||
@ -18,8 +17,8 @@ const (
|
|||||||
binOpenvpn26 = "openvpn2.6"
|
binOpenvpn26 = "openvpn2.6"
|
||||||
)
|
)
|
||||||
|
|
||||||
func start(ctx context.Context, starter command.Starter, version string, flags []string) (
|
func start(ctx context.Context, starter CmdStarter, version string, flags []string) (
|
||||||
stdoutLines, stderrLines chan string, waitError chan error, err error) {
|
stdoutLines, stderrLines <-chan string, waitError <-chan error, err error) {
|
||||||
var bin string
|
var bin string
|
||||||
switch version {
|
switch version {
|
||||||
case openvpn.Openvpn25:
|
case openvpn.Openvpn25:
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func streamLines(ctx context.Context, done chan<- struct{},
|
func streamLines(ctx context.Context, done chan<- struct{},
|
||||||
logger Logger, stdout, stderr chan string,
|
logger Logger, stdout, stderr <-chan string,
|
||||||
tunnelReady chan<- struct{}) {
|
tunnelReady chan<- struct{}) {
|
||||||
defer close(done)
|
defer close(done)
|
||||||
|
|
||||||
@ -16,10 +16,6 @@ func streamLines(ctx context.Context, done chan<- struct{},
|
|||||||
errLine := false
|
errLine := false
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
// Context should only be canceled after stdout and stderr are done
|
|
||||||
// being written to.
|
|
||||||
close(stdout)
|
|
||||||
close(stderr)
|
|
||||||
return
|
return
|
||||||
case line = <-stdout:
|
case line = <-stdout:
|
||||||
case line = <-stderr:
|
case line = <-stderr:
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package vpn
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
@ -92,3 +93,9 @@ type PublicIPLoop interface {
|
|||||||
RunOnce(ctx context.Context) (err error)
|
RunOnce(ctx context.Context) (err error)
|
||||||
ClearData() (err error)
|
ClearData() (err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CmdStarter interface {
|
||||||
|
Start(cmd *exec.Cmd) (
|
||||||
|
stdoutLines, stderrLines <-chan string,
|
||||||
|
waitError <-chan error, startErr error)
|
||||||
|
}
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/loopstate"
|
"github.com/qdm12/gluetun/internal/loopstate"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/gluetun/internal/vpn/state"
|
"github.com/qdm12/gluetun/internal/vpn/state"
|
||||||
"github.com/qdm12/golibs/command"
|
|
||||||
"github.com/qdm12/log"
|
"github.com/qdm12/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -32,7 +31,7 @@ type Loop struct {
|
|||||||
publicip PublicIPLoop
|
publicip PublicIPLoop
|
||||||
dnsLooper DNSLoop
|
dnsLooper DNSLoop
|
||||||
// Other objects
|
// Other objects
|
||||||
starter command.Starter // for OpenVPN
|
starter CmdStarter // for OpenVPN
|
||||||
logger log.LoggerInterface
|
logger log.LoggerInterface
|
||||||
client *http.Client
|
client *http.Client
|
||||||
// Internal channels and values
|
// Internal channels and values
|
||||||
@ -52,7 +51,7 @@ const (
|
|||||||
func NewLoop(vpnSettings settings.VPN, ipv6Supported bool, vpnInputPorts []uint16,
|
func NewLoop(vpnSettings settings.VPN, ipv6Supported bool, vpnInputPorts []uint16,
|
||||||
providers Providers, storage Storage, openvpnConf OpenVPN,
|
providers Providers, storage Storage, openvpnConf OpenVPN,
|
||||||
netLinker NetLinker, fw Firewall, routing Routing,
|
netLinker NetLinker, fw Firewall, routing Routing,
|
||||||
portForward PortForward, starter command.Starter,
|
portForward PortForward, starter CmdStarter,
|
||||||
publicip PublicIPLoop, dnsLooper DNSLoop,
|
publicip PublicIPLoop, dnsLooper DNSLoop,
|
||||||
logger log.LoggerInterface, client *http.Client,
|
logger log.LoggerInterface, client *http.Client,
|
||||||
buildInfo models.BuildInformation, versionInfo bool) *Loop {
|
buildInfo models.BuildInformation, versionInfo bool) *Loop {
|
||||||
|
|||||||
@ -7,14 +7,13 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/gluetun/internal/openvpn"
|
"github.com/qdm12/gluetun/internal/openvpn"
|
||||||
"github.com/qdm12/gluetun/internal/provider"
|
"github.com/qdm12/gluetun/internal/provider"
|
||||||
"github.com/qdm12/golibs/command"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// setupOpenVPN sets OpenVPN up using the configurators and settings given.
|
// setupOpenVPN sets OpenVPN up using the configurators and settings given.
|
||||||
// It returns a serverName for port forwarding (PIA) and an error if it fails.
|
// It returns a serverName for port forwarding (PIA) and an error if it fails.
|
||||||
func setupOpenVPN(ctx context.Context, fw Firewall,
|
func setupOpenVPN(ctx context.Context, fw Firewall,
|
||||||
openvpnConf OpenVPN, providerConf provider.Provider,
|
openvpnConf OpenVPN, providerConf provider.Provider,
|
||||||
settings settings.VPN, ipv6Supported bool, starter command.Starter,
|
settings settings.VPN, ipv6Supported bool, starter CmdStarter,
|
||||||
logger openvpn.Logger) (runner *openvpn.Runner, serverName string,
|
logger openvpn.Logger) (runner *openvpn.Runner, serverName string,
|
||||||
canPortForward bool, err error) {
|
canPortForward bool, err error) {
|
||||||
connection, err := providerConf.GetConnection(settings.Provider.ServerSelection, ipv6Supported)
|
connection, err := providerConf.GetConnection(settings.Provider.ServerSelection, ipv6Supported)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user