Skip to content
This repository has been archived by the owner on Feb 24, 2020. It is now read-only.

Commit

Permalink
run: add support for swappable engines
Browse files Browse the repository at this point in the history
This commit revamps `lib/run.go` to rely on an interface for the actual
execution of the desired command, such that alternate ways to run a
command in the container can be easily added. A flag has been added to
the run command to select a non-default engine. The default (and
currently only) engine is systemd-nspawn.
  • Loading branch information
Derek Gonyeo committed Mar 30, 2016
1 parent 16cd8b9 commit e224535
Show file tree
Hide file tree
Showing 10 changed files with 383 additions and 143 deletions.
5 changes: 3 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ language: go
matrix:
include:
- go: 1.4.2
- go: 1.5.1
- go: 1.5.3
- go: 1.6

install:
-

script:
- ./build
- ./test
- ./test -v

notifications:
email: false
Expand Down
18 changes: 12 additions & 6 deletions Documentation/subcommands/run.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,20 @@ run` is to be used on a system without overlayfs, the ACI and its dependencies
must be flattened into a single ACI without dependencies. A command called
`acbuild squash` is being worked on to do this.

## systemd-nspawn
## Engines

acbuild currently uses `systemd-nspawn` to run commands inside the ACI. This
means that the machine running acbuild must have systemd installed to be able
to use `acbuild run`. Alternate execution tools (like `runc`) will be added in
the future.
acbuild can use different engines to perform the actual execution of the given
command. The flag `--engine` can be used to select a non-default engine.

## Exiting out of systemd-nspawn
### systemd-nspawn

The default engine in acbuild is called `systemd-nspawn`, which rather
obviously uses `systemd-nspawn` to run the given command. This means that the
machine running acbuild must have systemd installed to be able to use `acbuild
run` with the default engine. Alternate execution tools (like `runc`) will be
added in the future.

### Exiting out of systemd-nspawn

All acbuild commands can be cancelled with Ctrl+c with the exception of
`acbuild run` once it has executed systemd-nspawn. To break out of a
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@ Instructions on how to do this are a little further down in this document.
acbuild requires a handful of commands be available on the system on
which it's run:

- `systemd-nspawn`
- `cp`
- `modprobe`
- `gpg`

Additionally `systemd-nspawn` is required to use the [default
engine](Documentation/subcommands/run.md) for acbuild run.

### Prebuilt Binaries

The easiest way to get `acbuild` is to download one of the
Expand Down
26 changes: 25 additions & 1 deletion acbuild/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,44 @@
package main

import (
"fmt"
"strings"

"github.com/appc/acbuild/engine"
"github.com/appc/acbuild/engine/systemdnspawn"

"github.com/spf13/cobra"
)

var (
insecure = false
workingdir = ""
engineName = ""
cmdRun = &cobra.Command{
Use: "run CMD [ARGS]",
Short: "Run a command in an ACI",
Long: "Run a given command in an ACI, and save the resulting container as a new ACI",
Example: "acbuild run yum install nginx",
Run: runWrapper(runRun),
}

engines = map[string]engine.Engine{
"systemd-nspawn": systemdnspawn.Engine{},
}
)

func init() {
cmdAcbuild.AddCommand(cmdRun)

var engineNames []string
for engine, _ := range engines {
engineNames = append(engineNames, engine)
}
engineList := fmt.Sprintf("[%s]", strings.Join(engineNames, ","))

cmdRun.Flags().BoolVar(&insecure, "insecure", false, "Allows fetching dependencies over http")
cmdRun.Flags().StringVar(&workingdir, "working-dir", "", "The working directory inside the container for this command")
cmdRun.Flags().StringVar(&engineName, "engine", "systemd-nspawn", "The engine used to run the command. Supported engines: "+engineList)
}

func runRun(cmd *cobra.Command, args []string) (exit int) {
Expand All @@ -47,7 +65,13 @@ func runRun(cmd *cobra.Command, args []string) (exit int) {
stderr("Running: %v", args)
}

err := newACBuild().Run(args, workingdir, insecure)
engine, ok := engines[engineName]
if !ok {
stderr("run: no such engine %q", engineName)
return 1
}

err := newACBuild().Run(args, workingdir, insecure, engine)

if err != nil {
stderr("run: %v", err)
Expand Down
32 changes: 32 additions & 0 deletions engine/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright 2015 The appc Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package engine

import (
"github.com/appc/spec/schema/types"
)

// Engine is an interface which is accepted by lib.Run, and used to perform the
// actual execution of a binary inside the container.
type Engine interface {
// Run executes a command inside a container. command is the path to the
// binary (post-chroot) to exec, args is the arguments to pass to the
// binary, environment is the set of environment variables to set for the
// binary, chroot is the path on the host where the container's root
// filesystem exists, and workingDir specifies the path inside the
// container that should be the current working directory for the binary.
// If workingDir is "", the default should be "/".
Run(command string, args []string, environment types.Environment, chroot, workingDir string) error
}
135 changes: 135 additions & 0 deletions engine/systemdnspawn/systemdnspawn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// Copyright 2015 The appc Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package systemdnspawn

import (
"fmt"
"os"
"os/exec"
"path"
"strings"
"syscall"

"github.com/appc/spec/schema/types"
)

var pathlist = []string{"/usr/local/sbin", "/usr/local/bin", "/usr/sbin",
"/usr/bin", "/sbin", "/bin"}

type Engine struct{}

func (e Engine) Run(command string, args []string, environment types.Environment, chroot, workingDir string) error {
nspawncmd := []string{"systemd-nspawn", "-D", chroot}

systemdVersion, err := getSystemdVersion()
if err != nil {
return err
}

if systemdVersion >= 209 {
nspawncmd = append(nspawncmd, "--quiet", "--register=no")
}
if workingDir != "" {
if systemdVersion < 229 {
return fmt.Errorf("the working dir can only be set on systems with systemd-nspawn >= 229")
}
nspawncmd = append(nspawncmd, "--chdir", workingDir)
}

for _, envVar := range environment {
nspawncmd = append(nspawncmd, "--setenv", envVar.Name+"="+envVar.Value)
}

nspawncmd = append(nspawncmd, "--setenv", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin")

abscmd, err := findCmdInPath(pathlist, command, chroot)
if err != nil {
return err
}

finfo, err := os.Lstat(path.Join(chroot, abscmd))
switch {
case os.IsNotExist(err):
return fmt.Errorf("binary %q doesn't exist", abscmd)
case err != nil:
return err
}

if finfo.Mode()&os.ModeSymlink != 0 && systemdVersion < 228 {
fmt.Fprintf(os.Stderr, "Warning: %q is a symlink, which systemd-nspawn version %d might error on\n", abscmd, systemdVersion)
}

nspawncmd = append(nspawncmd, abscmd)
nspawncmd = append(nspawncmd, args...)

execCmd := exec.Command(nspawncmd[0], nspawncmd[1:]...)
execCmd.Stdout = os.Stdout
execCmd.Stderr = os.Stderr
execCmd.Env = []string{"SYSTEMD_LOG_LEVEL=err"}

err = execCmd.Run()
if err == exec.ErrNotFound {
return fmt.Errorf("systemd-nspawn is required but not found")
}
if err == nil {
return nil
}
if exitErr, ok := err.(*exec.ExitError); ok {
code := exitErr.Sys().(syscall.WaitStatus).ExitStatus()
return fmt.Errorf("non-zero exit code: %d", code)
}
return err
}

func getSystemdVersion() (int, error) {
_, err := exec.LookPath("systemctl")
if err == exec.ErrNotFound {
return 0, fmt.Errorf("system does not have systemd")
}

blob, err := exec.Command("systemctl", "--version").Output()
if err != nil {
return 0, err
}
for _, line := range strings.Split(string(blob), "\n") {
if strings.HasPrefix(line, "systemd ") {
var version int
_, err := fmt.Sscanf(line, "systemd %d", &version)
if err != nil {
return 0, err
}
return version, nil
}
}
return 0, fmt.Errorf("error parsing output from `systemctl --version`")
}

func findCmdInPath(pathlist []string, cmd, prefix string) (string, error) {
if path.IsAbs(cmd) {
return cmd, nil
}

for _, p := range pathlist {
_, err := os.Lstat(path.Join(prefix, p, cmd))
switch {
case os.IsNotExist(err):
continue
case err != nil:
return "", err
}
return path.Join(p, cmd), nil
}
return "", fmt.Errorf("%s not found in any of: %v", cmd, pathlist)
}
Loading

0 comments on commit e224535

Please sign in to comment.