/
eopkg.go
329 lines (294 loc) · 8.43 KB
/
eopkg.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
//
// Copyright © 2016-2017 Ikey Doherty <ikey@solus-project.com>
//
// 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 builder
import (
"fmt"
log "github.com/Sirupsen/logrus"
"github.com/solus-project/libosdev/commands"
"github.com/solus-project/libosdev/disk"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
// eopkgCommand utility wraps all eopkg calls to autodisable colours
// where appropriate, as eopkg largely ignores the console type.
func eopkgCommand(c string) string {
if !DisableColors {
return c
}
return fmt.Sprintf("%s -N", c)
}
// An EopkgRepo is a simplistic representation of a repo found in any given
// chroot.
type EopkgRepo struct {
ID string
URI string
}
// EopkgManager is our own very shorted version of libosdev EopkgManager, to
// enable very very simple operations
type EopkgManager struct {
dbusActive bool
root string
cacheSource string
cacheTarget string
dbusPid string
notif PidNotifier
}
// NewEopkgManager will return a new eopkg manager
func NewEopkgManager(notif PidNotifier, root string) *EopkgManager {
return &EopkgManager{
dbusActive: false,
root: root,
cacheSource: PackageCacheDirectory,
cacheTarget: filepath.Join(root, "var/cache/eopkg/packages"),
dbusPid: filepath.Join(root, "var/run/dbus/pid"),
notif: notif,
}
}
// CopyAssets will copy any required host-side assets into the system. This
// function has to be reusable simply because performing an eopkg upgrade
// or installing deps, prior to building, could clobber the files.
func (e *EopkgManager) CopyAssets() error {
requiredAssets := map[string]string{
"/etc/resolv.conf": filepath.Join(e.root, "etc/resolv.conf"),
"/etc/eopkg/eopkg.conf": filepath.Join(e.root, "etc/eopkg/eopkg.conf"),
}
for key, value := range requiredAssets {
if !PathExists(key) {
continue
}
dirName := filepath.Dir(value)
if !PathExists(dirName) {
log.WithFields(log.Fields{
"dir": dirName,
}).Debug("Creating required directory")
if err := os.MkdirAll(dirName, 00755); err != nil {
log.WithFields(log.Fields{
"dir": dirName,
"error": err,
}).Error("Failed to create required asset directory")
return err
}
}
log.WithFields(log.Fields{
"file": key,
}).Debug("Copying host asset")
if err := disk.CopyFile(key, value); err != nil {
log.WithFields(log.Fields{
"file": key,
"error": err,
}).Error("Failed to copy host asset")
return err
}
}
return nil
}
// Init will do some basic preparation of the chroot
func (e *EopkgManager) Init() error {
// Ensure dbus pid is gone
if PathExists(e.dbusPid) {
if err := os.Remove(e.dbusPid); err != nil {
return err
}
}
if err := e.CopyAssets(); err != nil {
return err
}
// Ensure system wide cache exists
if !PathExists(e.cacheSource) {
log.WithFields(log.Fields{
"dir": e.cacheSource,
}).Debug("Creating system-wide package cache")
if err := os.MkdirAll(e.cacheSource, 00755); err != nil {
log.WithFields(log.Fields{
"dir": e.cacheSource,
"error": err,
}).Error("Failed to create package cache")
return err
}
}
if err := os.MkdirAll(e.cacheTarget, 00755); err != nil {
return err
}
return disk.GetMountManager().BindMount(e.cacheSource, e.cacheTarget)
}
// StartDBUS will bring up dbus within the chroot
func (e *EopkgManager) StartDBUS() error {
if e.dbusActive {
return nil
}
dbusDir := filepath.Join(e.root, "run", "dbus")
if err := os.MkdirAll(dbusDir, 00755); err != nil {
return err
}
if err := ChrootExec(e.notif, e.root, "dbus-uuidgen --ensure"); err != nil {
return err
}
e.notif.SetActivePID(0)
if err := ChrootExec(e.notif, e.root, "dbus-daemon --system"); err != nil {
return err
}
e.notif.SetActivePID(0)
e.dbusActive = true
return nil
}
// StopDBUS will tear down dbus
func (e *EopkgManager) StopDBUS() error {
// No sense killing dbus twice
if !e.dbusActive {
return nil
}
var b []byte
var err error
var f *os.File
if f, err = os.Open(e.dbusPid); err != nil {
return err
}
defer func() {
f.Close()
os.Remove(e.dbusPid)
e.dbusActive = false
}()
if b, err = ioutil.ReadAll(f); err != nil {
return err
}
pid := strings.Split(string(b), "\n")[0]
return commands.ExecStdoutArgs("kill", []string{"-9", pid})
}
// Cleanup will take care of any work we've already done before
func (e *EopkgManager) Cleanup() {
e.StopDBUS()
disk.GetMountManager().Unmount(e.cacheTarget)
}
// Upgrade will perform an eopkg upgrade inside the chroot
func (e *EopkgManager) Upgrade() error {
// Certain requirements may not be in system.base, but are required for
// proper containerized functionality.
newReqs := []string{
"iproute2",
}
if err := ChrootExec(e.notif, e.root, eopkgCommand("eopkg upgrade -y")); err != nil {
return err
}
e.notif.SetActivePID(0)
err := ChrootExec(e.notif, e.root, eopkgCommand(fmt.Sprintf("eopkg install -y %s", strings.Join(newReqs, " "))))
return err
}
// InstallComponent will install the named component inside the chroot
func (e *EopkgManager) InstallComponent(comp string) error {
err := ChrootExec(e.notif, e.root, eopkgCommand(fmt.Sprintf("eopkg install -c %v -y", comp)))
e.notif.SetActivePID(0)
return err
}
// EnsureEopkgLayout will enforce changes to the filesystem to make sure that
// it works as expected.
func EnsureEopkgLayout(root string) error {
// Ensures we don't end up with /var/lock vs /run/lock nonsense
reqDirs := []string{
"run/lock",
"var",
// Enables our bind mounting for caching
"var/cache/eopkg/packages",
}
// Now we must nuke /run if it exists inside the chroot!
runPath := filepath.Join(root, "run")
if PathExists(runPath) {
if err := os.RemoveAll(runPath); err != nil {
log.WithFields(log.Fields{
"error": err,
}).Error("Failed to clean stale /run")
return err
}
}
if err := os.MkdirAll(runPath, 00755); err != nil {
log.WithFields(log.Fields{
"error": err,
}).Error("Failed to clean create /run")
return err
}
// Construct the required directories in the tree
for _, dir := range reqDirs {
dirPath := filepath.Join(root, dir)
if err := os.MkdirAll(dirPath, 00755); err != nil {
return err
}
}
lockTgt := filepath.Join(root, "var", "lock")
if !PathExists(lockTgt) {
if err := os.Symlink("../run/lock", lockTgt); err != nil {
return err
}
}
runTgt := filepath.Join(root, "var", "run")
if !PathExists(runTgt) {
if err := os.Symlink("../run", filepath.Join(root, "var", "run")); err != nil {
return err
}
}
return nil
}
// Read the given plaintext URI file to find the target
func readURIFile(path string) (string, error) {
fi, err := os.Open(path)
if err != nil {
return "", err
}
defer fi.Close()
contents, err := ioutil.ReadAll(fi)
if err != nil {
return "", err
}
return string(contents), nil
}
// GetRepos will attempt to discover all the repos on the target filesystem
func (e *EopkgManager) GetRepos() ([]*EopkgRepo, error) {
globPat := filepath.Join(e.root, "var", "lib", "eopkg", "index", "*", "uri")
var repoFiles []string
log.Debug("Discovering repos in rootfs")
repoFiles, _ = filepath.Glob(globPat)
// No repos
if len(repoFiles) < 1 {
return nil, nil
}
var repos []*EopkgRepo
for _, repo := range repoFiles {
uri, err := readURIFile(repo)
if err != nil {
log.WithFields(log.Fields{
"error": err,
"path": repo,
}).Error("Unable to read repository file")
return nil, err
}
repoName := filepath.Base(filepath.Dir(repo))
repos = append(repos, &EopkgRepo{
ID: repoName,
URI: uri,
})
}
return repos, nil
}
// AddRepo will attempt to add a repo to the filesystem
func (e *EopkgManager) AddRepo(id, source string) error {
e.notif.SetActivePID(0)
return ChrootExec(e.notif, e.root, eopkgCommand(fmt.Sprintf("eopkg add-repo '%s' '%s'", id, source)))
}
// RemoveRepo will attempt to remove a named repo from the filesystem
func (e *EopkgManager) RemoveRepo(id string) error {
e.notif.SetActivePID(0)
return ChrootExec(e.notif, e.root, eopkgCommand(fmt.Sprintf("eopkg remove-repo '%s'", id)))
}