Skip to content

Commit b38e470

Browse files
committed
actions: install-dpkg: Implement action to install local Debian packages
The install-dpkg action allows local .deb packages to be installed using the apt command, much like the apt action but for local packages rather than for packages retrieved from remote apt repositories. Resolves: #157 Closes: #165 Signed-off-by: Christopher Obbard <[email protected]>
1 parent c740cbd commit b38e470

File tree

4 files changed

+188
-0
lines changed

4 files changed

+188
-0
lines changed

Diff for: README.md

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ Some of the actions provided by debos to customize and produce images are:
4141
* download: download a single file from the internet
4242
* filesystem-deploy: deploy a root filesystem to an image previously created
4343
* image-partition: create an image file, make partitions and format them
44+
* install-dpkg: install packages and their dependencies from local 'deb' files
4445
* ostree-commit: create an OSTree commit from rootfs
4546
* ostree-deploy: deploy an OSTree branch to the image
4647
* overlay: do a recursive copy of directories or files to the target filesystem

Diff for: actions/install_dpkg_action.go

+182
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/*
2+
InstallDpkg Action
3+
4+
Install packages from .deb files and their dependencies to the target rootfs
5+
using 'apt'.
6+
7+
Dependencies will be satisfied first from the `packages` list (i.e. locally
8+
available packages) and then from the target's configured apt repositories. If
9+
`deps` is set to false, dependencies will not be installed and an error will be
10+
thrown. TODO: check this
11+
12+
Attempting to downgrade packages which are already installed is not allowed and
13+
will throw an error.
14+
15+
# Yaml syntax:
16+
- action: install-dpkg
17+
origin: name
18+
recommends: bool
19+
unauthenticated: bool
20+
deps: bool
21+
packages:
22+
- package_path.deb
23+
- *.deb
24+
25+
Mandatory properties:
26+
27+
- packages -- list of package files to install from the filesystem (or named
28+
origin). Resolves Unix-style glob patterns. If installing from a named origin,
29+
e.g. the result of a download action, the package path will be automatically
30+
generated from the origin contents and the `packages` property can be omitted.
31+
32+
Optional properties:
33+
34+
- origin -- reference to named file or directory. Defaults to recipe directory.
35+
36+
- recommends -- boolean indicating if suggested packages will be installed. Defaults to false.
37+
38+
- unauthenticated -- boolean indicating if unauthenticated packages can be installed. Defaults to false.
39+
40+
- update -- boolean indicating if `apt update` will be run. Default 'true'.
41+
42+
Example to install all packages from recipe subdirectory `pkgs/`:
43+
44+
- action: install-dpkg
45+
description: Install Debian packages from local recipe
46+
packages:
47+
- pkgs/*.deb
48+
49+
Example to install named packages from recipe subdirectory `pkgs/`:
50+
51+
- action: install-dpkg
52+
description: Install Debian packages from local recipe
53+
packages:
54+
- pkgs/bmap-tools_*_all.deb
55+
- pkgs/fakemachine_*_amd64.deb
56+
57+
Example to download and install a package:
58+
59+
- action: download
60+
description: Install Debian package from url
61+
url: http://ftp.us.debian.org/debian/pool/main/b/bmap-tools/bmap-tools_3.5-2_all.deb
62+
name: bmap-tools-pkg
63+
64+
- action: install-dpkg
65+
description: Install Debian package from url
66+
origin: bmap-tools-pkg
67+
*/
68+
69+
package actions
70+
71+
import (
72+
"fmt"
73+
"log"
74+
"os"
75+
"path"
76+
"path/filepath"
77+
"strings"
78+
79+
"github.com/go-debos/debos"
80+
"github.com/go-debos/debos/wrapper"
81+
)
82+
83+
type InstallDpkgAction struct {
84+
debos.BaseAction `yaml:",inline"`
85+
Recommends bool
86+
Unauthenticated bool
87+
Update bool
88+
Origin string
89+
Packages []string
90+
}
91+
92+
func NewInstallDpkgAction() *InstallDpkgAction {
93+
a := &InstallDpkgAction{Update: true}
94+
return a
95+
}
96+
97+
func (apt *InstallDpkgAction) Run(context *debos.DebosContext) error {
98+
apt.LogStart()
99+
100+
aptCommand := wrapper.NewAptCommand(*context, "install-dpkg")
101+
102+
/* check if named origin exists or fallback to RecipeDir if no origin set */
103+
var origin string = context.RecipeDir
104+
if len(apt.Origin) > 0 {
105+
var found bool
106+
if origin, found = context.Origins[apt.Origin]; !found {
107+
return fmt.Errorf("origin %s not found", apt.Origin)
108+
}
109+
}
110+
111+
/* create a list of full paths of packages to install: if the origin is a
112+
* single file (e.g download action) then just return that package, otherwise
113+
* append package name to the origin path and glob to create a list of packages.
114+
* In other words, install all packages which are in the origin's directory.
115+
*/
116+
packages := []string{}
117+
file, err := os.Stat(origin)
118+
if err != nil {
119+
return err
120+
}
121+
122+
if file.IsDir() {
123+
if len(apt.Packages) == 0 {
124+
return fmt.Errorf("no packages defined")
125+
}
126+
127+
for _, pkg := range apt.Packages {
128+
// resolve globs
129+
source := path.Join(origin, pkg)
130+
matches, err := filepath.Glob(source)
131+
if err != nil {
132+
return err
133+
}
134+
if len(matches) == 0 {
135+
return fmt.Errorf("file(s) not found after globbing: %s", pkg)
136+
}
137+
138+
packages = append(packages, matches...)
139+
}
140+
} else {
141+
packages = append(packages, origin)
142+
}
143+
144+
/* bind mount each package into rootfs & update the list with the
145+
* path relative to the chroot */
146+
for idx, pkg := range packages {
147+
// check for duplicates after globbing
148+
for j := idx + 1; j < len(packages); j++ {
149+
if packages[j] == pkg {
150+
return fmt.Errorf("duplicate package found: %s", pkg)
151+
}
152+
}
153+
154+
log.Printf("Installing %s", pkg)
155+
156+
/* Only bind mount the package if the file is outside the rootfs */
157+
if strings.HasPrefix(pkg, context.Rootdir) {
158+
pkg = strings.TrimPrefix(pkg, context.Rootdir)
159+
} else {
160+
aptCommand.AddBindMount(pkg, "")
161+
}
162+
163+
/* update pkg list with the complete resolved path */
164+
packages[idx] = pkg
165+
}
166+
167+
if apt.Update {
168+
if err := aptCommand.Update(); err != nil {
169+
return err
170+
}
171+
}
172+
173+
if err := aptCommand.Install(packages, apt.Recommends, apt.Unauthenticated); err != nil {
174+
return err
175+
}
176+
177+
if err := aptCommand.Clean(); err != nil {
178+
return err
179+
}
180+
181+
return nil
182+
}

Diff for: actions/recipe.go

+4
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ Supported actions
5353
5454
- image-partition -- https://godoc.org/github.com/go-debos/debos/actions#hdr-ImagePartition_Action
5555
56+
- install-dpkg -- https://godoc.org/github.com/go-debos/debos/actions#hdr-InstallDpkg_Action
57+
5658
- ostree-commit -- https://godoc.org/github.com/go-debos/debos/actions#hdr-OstreeCommit_Action
5759
5860
- ostree-deploy -- https://godoc.org/github.com/go-debos/debos/actions#hdr-OstreeDeploy_Action
@@ -131,6 +133,8 @@ func (y *YamlAction) UnmarshalYAML(unmarshal func(interface{}) error) error {
131133
y.Action = &OverlayAction{}
132134
case "image-partition":
133135
y.Action = &ImagePartitionAction{}
136+
case "install-dpkg":
137+
y.Action = NewInstallDpkgAction()
134138
case "filesystem-deploy":
135139
y.Action = NewFilesystemDeployAction()
136140
case "raw":

Diff for: actions/recipe_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ actions:
5454
- action: download
5555
- action: filesystem-deploy
5656
- action: image-partition
57+
- action: install-dpkg
5758
- action: ostree-commit
5859
- action: ostree-deploy
5960
- action: overlay

0 commit comments

Comments
 (0)