Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

actions: install-deb: Implement action to install local Debian packages #220

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Some of the actions provided by debos to customize and produce images are:
* download: download a single file from the internet
* filesystem-deploy: deploy a root filesystem to an image previously created
* image-partition: create an image file, make partitions and format them
* install-dpkg: install packages and their dependencies from local 'deb' files
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

install-dpkg is rather weird for an action that install a .deb using apt :p Why not call it install-deb :)

* ostree-commit: create an OSTree commit from rootfs
* ostree-deploy: deploy an OSTree branch to the image
* overlay: do a recursive copy of directories or files to the target filesystem
Expand Down
40 changes: 5 additions & 35 deletions actions/apt_action.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ package actions

import (
"github.com/go-debos/debos"
"github.com/go-debos/debos/wrapper"
)

type AptAction struct {
Expand All @@ -44,50 +45,19 @@ func NewAptAction() *AptAction {
}

func (apt *AptAction) Run(context *debos.DebosContext) error {
aptConfig := []string{}

/* Don't show progress update percentages */
aptConfig = append(aptConfig, "-o=quiet::NoUpdate=1")

aptOptions := []string{"apt-get", "-y"}
aptOptions = append(aptOptions, aptConfig...)

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

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

aptOptions = append(aptOptions, "install")
aptOptions = append(aptOptions, apt.Packages...)

c := debos.NewChrootCommandForContext(*context)
c.AddEnv("DEBIAN_FRONTEND=noninteractive")
aptCommand := wrapper.NewAptCommand(*context, "apt")

if apt.Update {
cmd := []string{"apt-get"}
cmd = append(cmd, aptConfig...)
cmd = append(cmd, "update")

err := c.Run("apt", cmd...)
if err != nil {
if err := aptCommand.Update(); err != nil {
return err
}
}

err := c.Run("apt", aptOptions...)
if err != nil {
if err := aptCommand.Install(apt.Packages, apt.Recommends, apt.Unauthenticated); err != nil {
return err
}

cmd := []string{"apt-get"}
cmd = append(cmd, aptConfig...)
cmd = append(cmd, "clean")

err = c.Run("apt", cmd...)
if err != nil {
if err := aptCommand.Clean(); err != nil {
return err
}

Expand Down
180 changes: 180 additions & 0 deletions actions/install_dpkg_action.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/*
InstallDpkg Action

Install packages from .deb files and their dependencies to the target rootfs
using 'apt'.

Dependencies will be satisfied first from the `packages` list (i.e. locally
available packages) and then from the target's configured apt repositories. If
`deps` is set to false, dependencies will not be installed and an error will be
thrown. TODO: check this
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do the TODO :)


Attempting to downgrade packages which are already installed is not allowed and
will throw an error.

# Yaml syntax:
- action: install-dpkg
origin: name
recommends: bool
unauthenticated: bool
deps: bool
packages:
- package_path.deb
- *.deb

Mandatory properties:

- packages -- list of package files to install from the filesystem (or named
origin). Resolves Unix-style glob patterns. If installing from a named origin,
e.g. the result of a download action, the package path will be automatically
generated from the origin contents and the `packages` property can be omitted.

Optional properties:

- origin -- reference to named file or directory. Defaults to recipe directory.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

origin shouldn't be a named file or a directory but an origin (which i think has some documentation elsewhere)


- recommends -- boolean indicating if suggested packages will be installed. Defaults to false.

- unauthenticated -- boolean indicating if unauthenticated packages can be installed. Defaults to false.

- update -- boolean indicating if `apt update` will be run. Default 'true'.

Example to install all packages from recipe subdirectory `pkgs/`:

- action: install-dpkg
description: Install Debian packages from local recipe
packages:
- pkgs/*.deb

Example to install named packages from recipe subdirectory `pkgs/`:

- action: install-dpkg
description: Install Debian packages from local recipe
packages:
- pkgs/bmap-tools_*_all.deb
- pkgs/fakemachine_*_amd64.deb

Example to download and install a package:

- action: download
description: Install Debian package from url
url: http://ftp.us.debian.org/debian/pool/main/b/bmap-tools/bmap-tools_3.5-2_all.deb
name: bmap-tools-pkg

- action: install-dpkg
description: Install Debian package from url
origin: bmap-tools-pkg
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no packages listen in this example?

*/

package actions

import (
"fmt"
"log"
"os"
"path"
"path/filepath"
"strings"

"github.com/go-debos/debos"
"github.com/go-debos/debos/wrapper"
)

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

func NewInstallDpkgAction() *InstallDpkgAction {
a := &InstallDpkgAction{Update: true}
return a
Comment on lines +93 to +94
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
a := &InstallDpkgAction{Update: true}
return a
return &InstallDpkgAction{Update: true}

}

func (apt *InstallDpkgAction) Run(context *debos.DebosContext) error {
aptCommand := wrapper.NewAptCommand(*context, "install-dpkg")

/* check if named origin exists or fallback to RecipeDir if no origin set */
var origin string = context.RecipeDir
if len(apt.Origin) > 0 {
var found bool
if origin, found = context.Origins[apt.Origin]; !found {
return fmt.Errorf("origin %s not found", apt.Origin)
}
}

/* 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.
* In other words, install all packages which are in the origin's directory.
*/
packages := []string{}
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)
}

packages = append(packages, matches...)
}
} else {
packages = append(packages, origin)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if the user passed a Package list?

}

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not dedup the list rather then erroring out ?

}
}

log.Printf("Installing %s", pkg)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're printing Installing; but there is a bunch of stuff and commands being run before things are actually installed


/* Only bind mount the package if the file is outside the rootfs */
if strings.HasPrefix(pkg, context.Rootdir) {
pkg = strings.TrimPrefix(pkg, context.Rootdir)
} else {
aptCommand.AddBindMount(pkg, "")
}

/* update pkg list with the complete resolved path */
packages[idx] = pkg
}

if apt.Update {
if err := aptCommand.Update(); err != nil {
return err
}
}

if err := aptCommand.Install(packages, apt.Recommends, apt.Unauthenticated); err != nil {
return err
}

if err := aptCommand.Clean(); 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 @@ -54,6 +54,8 @@ Supported actions

- image-partition -- https://godoc.org/github.com/go-debos/debos/actions#hdr-ImagePartition_Action

- install-dpkg -- https://godoc.org/github.com/go-debos/debos/actions#hdr-InstallDpkg_Action

- ostree-commit -- https://godoc.org/github.com/go-debos/debos/actions#hdr-OstreeCommit_Action

- ostree-deploy -- https://godoc.org/github.com/go-debos/debos/actions#hdr-OstreeDeploy_Action
Expand Down Expand Up @@ -133,6 +135,8 @@ func (y *YamlAction) UnmarshalYAML(unmarshal func(interface{}) error) error {
y.Action = &OverlayAction{}
case "image-partition":
y.Action = &ImagePartitionAction{}
case "install-dpkg":
y.Action = NewInstallDpkgAction()
case "filesystem-deploy":
y.Action = NewFilesystemDeployAction()
case "raw":
Expand Down
1 change: 1 addition & 0 deletions actions/recipe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ actions:
- action: download
- action: filesystem-deploy
- action: image-partition
- action: install-dpkg
- action: ostree-commit
- action: ostree-deploy
- action: overlay
Expand Down
49 changes: 49 additions & 0 deletions wrapper/apt_wrapper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/* Abstracts the apt command. */
package wrapper

import (
"github.com/go-debos/debos"
)

type AptCommand struct {
Wrapper
}

func NewAptCommand(context debos.DebosContext, label string) AptCommand {
command := "apt-get"

apt := AptCommand{
Wrapper: NewCommandWrapper(context, command, label),
}

apt.AddEnv("DEBIAN_FRONTEND=noninteractive")

/* Don't show progress update percentages */
apt.AppendGlobalArguments("-o=quiet::NoUpdate=1")

return apt
}

func (apt AptCommand) Clean() error {
return apt.Run("clean")
}

func (apt AptCommand) Install(packages []string, recommends bool, unauthenticated bool) error {
arguments := []string{"install", "--yes"}

if !recommends {
arguments = append(arguments, "--no-install-recommends")
}

if unauthenticated {
arguments = append(arguments, "--allow-unauthenticated")
}

arguments = append(arguments, packages...)

return apt.Run(arguments...)
}

func (apt AptCommand) Update() error {
return apt.Run("update")
}
41 changes: 41 additions & 0 deletions wrapper/wrapper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/* Base class to abstract commonly used commands. */
package wrapper

import (
"github.com/go-debos/debos"
)

type Wrapper struct {
debos.Command
command string
globalArgs []string
label string
}

func NewCommandWrapper(context debos.DebosContext, command string, label string) Wrapper {
return Wrapper{
Command: debos.NewChrootCommandForContext(context),
command: command,
label: label,
}
}

func (cmd *Wrapper) SetCommand(command string) {
cmd.command = command
}

func (cmd *Wrapper) AppendGlobalArguments(args string) {
cmd.globalArgs = append(cmd.globalArgs, args)
}

func (cmd *Wrapper) SetLabel(label string) {
cmd.label = label
}

func (cmd Wrapper) Run(additionalArgs ...string) error {
args := []string{cmd.command}
args = append(args, cmd.globalArgs...)
args = append(args, additionalArgs...)

return cmd.Command.Run(cmd.label, args...)
}