1
2 package systemd
3
4 import (
5 "context"
6 "errors"
7 "fmt"
8 "path/filepath"
9 "strings"
10
11 "github.com/coreos/go-systemd/v22/dbus"
12 )
13
14 const (
15
16
17 Replace Mode = "replace"
18
19
20 Fail Mode = "fail"
21
22
23 Isolate Mode = "isolate"
24
25
26 IgnoreDependencies Mode = "ignore-dependencies"
27
28
29 IgnoreRequirements Mode = "ignore-requirements"
30 )
31
32 type Mode string
33
34
35 type Connection interface {
36 StatusChecker
37 Restarter
38 Starter
39 Stopper
40 Disabler
41 Closer
42 }
43
44
45 type StatusChecker interface {
46 ActiveState(ctx context.Context, service string) (string, error)
47 SubState(ctx context.Context, service string) (string, error)
48 Exists(ctx context.Context, service string) (bool, error)
49 }
50
51
52 type Starter interface {
53 Start(ctx context.Context, service string, mode Mode, failOnSkipped bool) error
54 StartTransient(ctx context.Context, service string, mode Mode, properties []dbus.Property, failOnSkipped bool) error
55 }
56
57
58 type Restarter interface {
59 Restart(ctx context.Context, service string, mode Mode, failOnSkipped bool) error
60 }
61
62
63 type Stopper interface {
64 Stop(ctx context.Context, service string, mode Mode, failOnSkipped bool) error
65 }
66
67
68 type Disabler interface {
69 Disable(ctx context.Context, services []string) error
70 }
71
72
73 type Closer interface {
74 Close()
75 }
76
77
78
79 type connection struct {
80 *dbus.Conn
81 }
82
83
84
85 func NewConnection(ctx context.Context) (Connection, error) {
86 conn, err := dbus.NewSystemdConnectionContext(ctx)
87 if err != nil {
88 return nil, fmt.Errorf("failed to establish connection to systemd: %w", err)
89 }
90 return &connection{conn}, nil
91 }
92
93
94 func (c *connection) ActiveState(ctx context.Context, service string) (string, error) {
95 state, err := c.GetUnitPropertyContext(ctx, service, "ActiveState")
96 if err != nil {
97 return "", err
98 }
99
100 return strings.Trim(state.Value.String(), "\""), nil
101 }
102
103
104 func (c *connection) SubState(ctx context.Context, service string) (string, error) {
105 state, err := c.GetUnitPropertyContext(ctx, service, "SubState")
106 if err != nil {
107 return "", err
108 }
109
110 return strings.Trim(state.Value.String(), "\""), nil
111 }
112
113
114
115
116
117
118
119 func (c *connection) Start(ctx context.Context, service string, mode Mode, failOnSkipped bool) error {
120 resp := make(chan string)
121 if _, err := c.StartUnitContext(ctx, service, string(mode), resp); err != nil {
122 return err
123 }
124 jobResp := <-resp
125
126 return handleResponse(jobResp, failOnSkipped)
127 }
128
129
130
131
132
133
134
135 func (c *connection) StartTransient(ctx context.Context, service string, mode Mode, properties []dbus.Property, failOnSkipped bool) error {
136 resp := make(chan string)
137 if _, err := c.StartTransientUnitContext(ctx, service, string(mode), properties, resp); err != nil {
138 return err
139 }
140 jobResp := <-resp
141
142 return handleResponse(jobResp, failOnSkipped)
143 }
144
145
146
147
148
149
150
151 func (c *connection) Restart(ctx context.Context, service string, mode Mode, failOnSkipped bool) error {
152 resp := make(chan string)
153 if _, err := c.RestartUnitContext(ctx, service, string(mode), resp); err != nil {
154 return err
155 }
156 jobResp := <-resp
157
158 return handleResponse(jobResp, failOnSkipped)
159 }
160
161
162
163
164
165
166
167 func (c *connection) Stop(ctx context.Context, service string, mode Mode, failOnSkipped bool) error {
168 resp := make(chan string)
169 if _, err := c.StopUnitContext(ctx, service, string(mode), resp); err != nil {
170 return err
171 }
172 jobResp := <-resp
173
174 return handleResponse(jobResp, failOnSkipped)
175 }
176
177
178
179 func handleResponse(resp string, failOnSkipped bool) error {
180 switch resp {
181 case "done":
182 return nil
183 case "skipped":
184 if failOnSkipped {
185 return errors.New("job skipped")
186 }
187 return nil
188 default:
189 return fmt.Errorf("error restarting service: %v", resp)
190 }
191 }
192
193
194 func (c *connection) Exists(ctx context.Context, service string) (bool, error) {
195 unitFiles, err := c.ListUnitFilesContext(ctx)
196 if err != nil {
197 return false, fmt.Errorf("failed to contact systemd bus: %w", err)
198 }
199
200 for _, unit := range unitFiles {
201 if filepath.Base(unit.Path) == service {
202 return true, nil
203 }
204 }
205
206 return false, nil
207 }
208
209
210 func (c *connection) Disable(ctx context.Context, services []string) error {
211 _, err := c.DisableUnitFilesContext(ctx, services, false)
212 return err
213 }
214
View as plain text