Skip to content

Commit

Permalink
actions: apt-file: Install Debian packages from the filesystem
Browse files Browse the repository at this point in the history
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: go-debos#157
Closes: go-debos#165

Signed-off-by: Christopher Obbard <[email protected]>
  • Loading branch information
obbardc committed Jul 23, 2021
1 parent c66a48d commit 2cdd04b
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ of each other.

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

* apt-file: installs packages and their dependencies from local 'deb' files
* apt: install packages and their dependencies with 'apt'
* debootstrap: construct the target rootfs with debootstrap
* download: download a single file from the internet
Expand Down
170 changes: 170 additions & 0 deletions actions/apt_file_action.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/*
AptFile Action
Install packages from .deb files and their dependencies to the target rootfs
with 'apt'.
Dependencies will be satisfied first from the package list and then from the
target's configured apt repositories.
Attempting to downgrade packages which are already installed is not allowed and
will throw an error.
Yaml syntax:
- action: apt-file
origin: name
recommends: bool
unauthenticated: bool
packages:
- package_path.deb
- *.deb
Mandatory properties:
- packages -- list of packages to install. Resolves Unix-style glob patterns.
Optional properties:
- origin -- reference to named file or directory. Defaults to recipe directory.
- recommends -- boolean indicating if suggested packages will be installed. Defaults to false.
- unauthenticated -- boolean indicating if unauthenticated packages can be installed. Defaults to false.
Example to install named packages in a subdirectory under `debs/`:
- action: apt-file
description: Test install from file
packages:
- pkgs/bmap-tools_*_all.deb
- pkgs/fakemachine_*_amd64.deb
Example to download and install a package:
- action: download
url: http://ftp.us.debian.org/debian/pool/main/b/bmap-tools/bmap-tools_3.5-2_all.deb
name: bmap-tools-pkg
- action: apt-file
description: Test install from download
origin: bmap-tools-pkg
*/

package actions

import (
"fmt"
"os"
"path"
"path/filepath"
"strings"
"github.com/go-debos/debos"
)

type AptFileAction struct {
debos.BaseAction `yaml:",inline"`
Recommends bool
Unauthenticated bool
Origin string
Packages []string
}

func (apt *AptFileAction) Run(context *debos.DebosContext) error {
apt.LogStart()
var origin string
aptOptions := []string{"apt-get", "--yes"}
pkgs := []string{}

c := debos.NewChrootCommandForContext(*context)
c.AddEnv("DEBIAN_FRONTEND=noninteractive")

// get the full path of a named origin
if len(apt.Origin) > 0 {
var found bool
if origin, found = context.Origins[apt.Origin]; !found {
return fmt.Errorf("Origin not found '%s'", apt.Origin)
}
} else {
// otherwise fallback to RecipeDir
origin = context.RecipeDir
}

/* create a list of full paths of packages to install: if the origin is a
* single file (e.g download action) then just return that package, otherwise
* append package name to the origin path and glob to create a list of packages */
file, err := os.Stat(origin)
if err != nil {
return err
}
if file.IsDir() {
if len(apt.Packages) == 0 {
return fmt.Errorf("No packages defined")
}

for _, pkg := range apt.Packages {
// resolve globs
source := path.Join(origin, pkg)
matches, err := filepath.Glob(source)
if err != nil {
return err
}
if len(matches) == 0 {
return fmt.Errorf("File(s) not found after globbing: %s", pkg)
}

pkgs = append(pkgs, matches...)
}
} else {
pkgs = append(pkgs, origin)
}

/* bind mount each package into rootfs & update the list with the
* path relative to the chroot */
for idx, pkg := range pkgs {
// check for duplicates after globbing
for j := idx + 1; j < len(pkgs); j++ {
if pkgs[j] == pkg {
return fmt.Errorf("Duplicate package found: %s", pkg)
}
}

// only bind-mount if the package is outside the rootfs
if strings.HasPrefix(pkg, context.Rootdir) {
pkg = strings.TrimPrefix(pkg, context.Rootdir)
} else {
c.AddBindMount(pkg, "")
}

// update pkgs with the resolved path
pkgs[idx] = "." + pkg
}

err = c.Run("apt-file", "apt-get", "update")
if err != nil {
return err
}

if !apt.Recommends {
aptOptions = append(aptOptions, "--no-install-recommends")
}

if apt.Unauthenticated {
aptOptions = append(aptOptions, "--allow-unauthenticated")
}

aptOptions = append(aptOptions, "install")
aptOptions = append(aptOptions, pkgs...)
err = c.Run("apt-file", aptOptions...)
if err != nil {
return err
}

err = c.Run("apt-file", "apt-get", "clean")
if err != nil {
return err
}

return nil
}
4 changes: 4 additions & 0 deletions actions/recipe.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ Mandatory properties for receipt:
Supported actions
- apt-file -- https://godoc.org/github.com/go-debos/debos/actions#hdr-AptFile_Action
- apt -- https://godoc.org/github.com/go-debos/debos/actions#hdr-Apt_Action
- debootstrap -- https://godoc.org/github.com/go-debos/debos/actions#hdr-Debootstrap_Action
Expand Down Expand Up @@ -106,6 +108,8 @@ func (y *YamlAction) UnmarshalYAML(unmarshal func(interface{}) error) error {
y.Action = &UnpackAction{}
case "run":
y.Action = &RunAction{}
case "apt-file":
y.Action = &AptFileAction{}
case "apt":
y.Action = NewAptAction()
case "ostree-commit":
Expand Down
1 change: 1 addition & 0 deletions actions/recipe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func TestParse_syntax(t *testing.T) {
architecture: arm64
actions:
- action: apt-file
- action: apt
- action: debootstrap
- action: download
Expand Down

0 comments on commit 2cdd04b

Please sign in to comment.