-
Notifications
You must be signed in to change notification settings - Fork 139
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
} | ||||||||
|
||||||||
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) | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||||||||
} |
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") | ||
} |
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...) | ||
} |
There was a problem hiding this comment.
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
:)