Skip to content

Commit eb4cf97

Browse files
committed
actions: apt-file: initial implementation of action to install local debian packages
The apt-file action allows local .deb packages to be installed using the `apt` command, much like the apt action but for local packages rather than those from apt repositories. Resolves: #157 Closes: #165 Signed-off-by: Christopher Obbard <[email protected]>
1 parent ee15bc4 commit eb4cf97

File tree

4 files changed

+176
-0
lines changed

4 files changed

+176
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ of each other.
3131

3232
Some of the actions provided by debos to customize and produce images are:
3333

34+
* apt-file: installs packages and their dependencies from local 'deb' files
3435
* apt: install packages and their dependencies with 'apt'
3536
* debootstrap: construct the target rootfs with debootstrap
3637
* download: download a single file from the internet

actions/apt_file_action.go

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

actions/recipe.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ Mandatory properties for receipt:
3737
3838
Supported actions
3939
40+
- apt-file -- https://godoc.org/github.com/go-debos/debos/actions#hdr-AptFile_Action
41+
4042
- apt -- https://godoc.org/github.com/go-debos/debos/actions#hdr-Apt_Action
4143
4244
- debootstrap -- https://godoc.org/github.com/go-debos/debos/actions#hdr-Debootstrap_Action
@@ -106,6 +108,8 @@ func (y *YamlAction) UnmarshalYAML(unmarshal func(interface{}) error) error {
106108
y.Action = &UnpackAction{}
107109
case "run":
108110
y.Action = &RunAction{}
111+
case "apt-file":
112+
y.Action = &AptFileAction{}
109113
case "apt":
110114
y.Action = &AptAction{}
111115
case "ostree-commit":

actions/recipe_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ func TestParse_syntax(t *testing.T) {
4949
architecture: arm64
5050
5151
actions:
52+
- action: apt-file
5253
- action: apt
5354
- action: debootstrap
5455
- action: download

0 commit comments

Comments
 (0)