Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/systemd unit enable #613

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions resource/systemd/unit/dbus_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,8 @@ type SystemdConnection interface {

// KillUnit sends a unix signal to the process
KillUnit(name string, signal int32)

EnableUnitFiles(files []string, runtime bool, force bool) (bool, []dbus.EnableUnitFileChange, error)

DisableUnitFiles(files []string, runtime bool) ([]dbus.DisableUnitFileChange, error)
}
6 changes: 6 additions & 0 deletions resource/systemd/unit/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ type SystemdExecutor interface {
// will only work on systemd-aware processes.
ReloadUnit(*Unit) error

// EnableUnit will enable the unit file and return a list of any changes
EnableUnit(whichUnit *Unit, runtime, force bool) (bool, []*unitFileChange, error)

// DisableUnit will disable the unit file and return a list of any changes
DisableUnit(whichUnit *Unit, runtime bool) ([]*unitFileChange, error)

// Send a unix signal to a process.
SendSignal(u *Unit, signal Signal)
}
12 changes: 12 additions & 0 deletions resource/systemd/unit/executor_mock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,15 @@ func (m *ExecutorMock) SendSignal(u *Unit, signal Signal) {
m.Called(u, signal)
return
}

func (m *ExecutorMock) EnableUnit(u *Unit, runtime, force bool) (bool, []*unitFileChange, error) {
m.maybeSleep()
args := m.Called(u, runtime, force)
return args.Bool(0), args.Get(1).([]*unitFileChange), args.Error(2)
}

func (m *ExecutorMock) DisableUnit(u *Unit, runtime bool) ([]*unitFileChange, error) {
m.maybeSleep()
args := m.Called(u, runtime)
return args.Get(0).([]*unitFileChange), args.Error(1)
}
37 changes: 37 additions & 0 deletions resource/systemd/unit/fsexecutor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright © 2017 Asteris, LLC
//
// 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 unit

import (
"os"
"path/filepath"
)

type fsexecutor interface {
EvalSymlinks(path string) (string, error)
Walk(root string, f func(string, os.FileInfo, error) error) error
}

type realFsExecutor struct{}

func (r realFsExecutor) EvalSymlinks(path string) (string, error) {
return filepath.EvalSymlinks(path)
}

func (r realFsExecutor) Walk(root string, f func(string, os.FileInfo, error) error) error {
return filepath.Walk(root, f)
}

func filesystemExecutor() fsexecutor { return realFsExecutor{} }
74 changes: 74 additions & 0 deletions resource/systemd/unit/fsexecutor_mock_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright © 2017 Asteris, LLC
//
// 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 unit

import (
"os"
"strings"
"time"

"github.com/stretchr/testify/mock"
)

type walkFuncArgs struct {
path string
info os.FileInfo
err error
}

type mockFsExecutor struct {
mock.Mock
walkWith []walkFuncArgs
}

func (m *mockFsExecutor) EvalSymlinks(path string) (string, error) {
args := m.Called(path)
return args.String(0), args.Error(1)
}

func (m *mockFsExecutor) Walk(root string, f func(string, os.FileInfo, error) error) error {
args := m.Called(root, f)
for _, node := range m.walkWith {
if !strings.HasPrefix(node.path, root) {
continue
}
f(node.path, node.info, node.err)
}
return args.Error(0)
}

func newMockWithPaths(path ...string) *mockFsExecutor {
var args []walkFuncArgs
for _, p := range path {
a := walkFuncArgs{
path: p,
err: nil,
info: mockFileInfo{p},
}
args = append(args, a)
}
return &mockFsExecutor{walkWith: args}
}

type mockFileInfo struct {
path string
}

func (n mockFileInfo) Name() string { s := strings.Split(n.path, "/"); return s[len(s)-1] }
func (n mockFileInfo) Size() int64 { return 0 }
func (n mockFileInfo) Mode() os.FileMode { return 0777 }
func (n mockFileInfo) ModTime() time.Time { return time.Now() }
func (n mockFileInfo) IsDir() bool { return false }
func (n mockFileInfo) Sys() interface{} { return nil }
10 changes: 10 additions & 0 deletions resource/systemd/unit/linux_mocks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ func (m *DbusMock) ListUnits() ([]dbus.UnitStatus, error) {
return args.Get(0).([]dbus.UnitStatus), args.Error(1)
}

func (m *DbusMock) EnableUnitFiles(files []string, runtime, force bool) (bool, []dbus.EnableUnitFileChange, error) {
args := m.Called(files, runtime, force)
return args.Bool(0), args.Get(1).([]dbus.EnableUnitFileChange), args.Error(2)
}

func (m *DbusMock) DisableUnitFiles(files []string, runtime bool) ([]dbus.DisableUnitFileChange, error) {
args := m.Called(files, runtime)
return args.Get(1).([]dbus.DisableUnitFileChange), args.Error(2)
}

// ListUnits mocks ListUnitsByNames
func (m *DbusMock) ListUnitsByNames(names []string) ([]dbus.UnitStatus, error) {
args := m.Called(names)
Expand Down
32 changes: 26 additions & 6 deletions resource/systemd/unit/preparer.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,17 @@ import (
// UnitState configures loaded systemd units, allowing you to start, stop, or
// restart them, reload configuration files, and send unix signals.
type Preparer struct {
// The name of the unit. This may optionally include the unit type,
// e.g. "foo.service" and "foo" are both valid.
// The name of the unit. This may optionally omit the unit type if there is
// only a single unit type of the given name. e.g. "foo.service" and "foo"
// are both valid if, and only if, no other unit type named "foo" exists.
Name string `hcl:"unit" required:"true"`

// The full path to the unit. If path is specified then it will be used when
// determining if the unit has been enabled or disabled. Note that this path
// must exist within one of the normal systemd search directories
// (e.g. `/lib/systemd/system`)
Path string `hcl:"path"`

// The desired state of the unit. This will affect the current unit job. Use
// `systemd.unit.file` to enable and disable jobs, or `systemd.unit.config` to
// set options.
Expand All @@ -53,6 +60,16 @@ type Preparer struct {
// an unsigned integer value between 1 and 31 inclusive.
SignalNumber uint `hcl:"signal_number" mutually_exclusive:"signal_name,signal_num"`

// Specifies that a unit file should be persistently enabled or disabled. If
// true, enable the unit, if false, disable it, otherwise leave the current
// settings unmodified.
Enable *bool `hcl:"enabled"`

// Specifies that a unit file should be temporarily enabled or disabled. If
// true, enable the unit, if false, disable it, otherwise leave the current
// settings unmodified.
EnableRuntime *bool `hcl:"enabled_runtime"`

executor SystemdExecutor
}

Expand Down Expand Up @@ -82,10 +99,13 @@ func (p *Preparer) Prepare(ctx context.Context, render resource.Renderer) (resou
}

r := &Resource{
Reload: p.Reload,
Name: p.Name,
State: p.State,
systemdExecutor: p.executor,
Reload: p.Reload,
Name: p.Name,
State: p.State,
systemdExecutor: p.executor,
enableChange: p.Enable,
enableRuntimeChange: p.EnableRuntime,
fs: realFsExecutor{},
}

if signal != nil {
Expand Down
86 changes: 82 additions & 4 deletions resource/systemd/unit/preparer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,14 @@ func TestPreparer(t *testing.T) {
})
t.Run("sets-fields", func(t *testing.T) {
t.Parallel()
untrue := false
res, err := (&Preparer{
Name: "test1",
State: "state1",
Reload: true,
executor: &ExecutorMock{},
Name: "test1",
State: "state1",
Reload: true,
Enable: &untrue,
EnableRuntime: &untrue,
executor: &ExecutorMock{},
}).Prepare(context.Background(), fakerenderer.New())
require.NoError(t, err)
assert.Equal(t, "test1", res.(*Resource).Name)
Expand All @@ -132,6 +135,81 @@ func TestPreparer(t *testing.T) {
assert.False(t, res.(*Resource).sendSignal)
assert.Equal(t, "", res.(*Resource).SignalName)
assert.Equal(t, uint(0), res.(*Resource).SignalNumber)
assert.False(t, *res.(*Resource).enableChange)
assert.False(t, *res.(*Resource).enableRuntimeChange)
})
t.Run("handles-enable-disable", func(t *testing.T) {
t.Parallel()
valTrue := true
valFalse := false
t.Run("when-true-true", func(t *testing.T) {
t.Parallel()
_, err := (&Preparer{
Enable: &valTrue,
EnableRuntime: &valTrue,
executor: &ExecutorMock{},
}).Prepare(context.Background(), fakerenderer.New())
assert.NoError(t, err)
})
t.Run("when-false-true", func(t *testing.T) {
t.Parallel()
_, err := (&Preparer{
Enable: &valFalse,
EnableRuntime: &valTrue,
executor: &ExecutorMock{},
}).Prepare(context.Background(), fakerenderer.New())
assert.NoError(t, err)
})
t.Run("when-true-false", func(t *testing.T) {
t.Parallel()
_, err := (&Preparer{
Enable: &valTrue,
EnableRuntime: &valFalse,
executor: &ExecutorMock{},
}).Prepare(context.Background(), fakerenderer.New())
assert.NoError(t, err)
})
t.Run("when-false-false", func(t *testing.T) {
t.Parallel()
_, err := (&Preparer{
Enable: &valFalse,
EnableRuntime: &valFalse,
executor: &ExecutorMock{},
}).Prepare(context.Background(), fakerenderer.New())
assert.NoError(t, err)
})
t.Run("when-true-nil", func(t *testing.T) {
t.Parallel()
_, err := (&Preparer{
Enable: &valTrue,
executor: &ExecutorMock{},
}).Prepare(context.Background(), fakerenderer.New())
assert.NoError(t, err)
})
t.Run("when-false-nil", func(t *testing.T) {
t.Parallel()
_, err := (&Preparer{
Enable: &valFalse,
executor: &ExecutorMock{},
}).Prepare(context.Background(), fakerenderer.New())
assert.NoError(t, err)
})
t.Run("when-nil-true", func(t *testing.T) {
t.Parallel()
_, err := (&Preparer{
EnableRuntime: &valTrue,
executor: &ExecutorMock{},
}).Prepare(context.Background(), fakerenderer.New())
assert.NoError(t, err)
})
t.Run("when-nil-alse", func(t *testing.T) {
t.Parallel()
_, err := (&Preparer{
EnableRuntime: &valFalse,
executor: &ExecutorMock{},
}).Prepare(context.Background(), fakerenderer.New())
assert.NoError(t, err)
})
})

}
Loading