Skip to content

Commit

Permalink
virter: pass-through exit code from provisioning
Browse files Browse the repository at this point in the history
When a provisioning step fails, it may be helpful to the caller of
virter to know the exit code of the script.

So, if an SSH or container provisioning step fails with an exit code,
pass it through to the CLI, exiting from the virter process with the
same exit code.
  • Loading branch information
chrboe committed Dec 28, 2023
1 parent 697988e commit bbe9075
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 3 deletions.
2 changes: 1 addition & 1 deletion cmd/image_build.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ func imageBuildCommand() *cobra.Command {

err = v.ImageBuild(ctx, tools, vmConfig, getReadyConfig(), buildConfig, virter.WithProgress(DefaultProgressFormat(p)))
if err != nil {
log.Fatalf("Failed to build image: %v", err)
logProvisioningErrorAndExit(err)
}

if push {
Expand Down
23 changes: 22 additions & 1 deletion cmd/vm_exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ package cmd

import (
"context"
"errors"
"fmt"
"os"
"strings"

"golang.org/x/crypto/ssh"

log "github.com/sirupsen/logrus"

"github.com/LINBIT/containerapi"
Expand All @@ -16,6 +20,23 @@ import (
"github.com/spf13/cobra"
)

// logProvisioningErrorAndExit logs an error from a virter.VMExec* function and exits with the appropriate exit code.
// If the error is from a failed SSH or container provisioning step, the exit code is the exit code
// of the respective command.
// Otherwise, the exit code is 1.
func logProvisioningErrorAndExit(err error) {
log.Errorf("Failed to build image: %v", err)
var sshErr *ssh.ExitError
if errors.As(err, &sshErr) {
os.Exit(sshErr.ExitStatus())
}
var containerErr *virter.ContainerExitError
if errors.As(err, &containerErr) {
os.Exit(containerErr.Status)
}
os.Exit(1)
}

func vmExecCommand() *cobra.Command {
var provisionFile string
var provisionOverrides []string
Expand All @@ -35,7 +56,7 @@ func vmExecCommand() *cobra.Command {
OverridePullPolicy: containerPullPolicy,
}
if err := execProvision(cmd.Context(), provOpt, args); err != nil {
log.Fatal(err)
logProvisioningErrorAndExit(err)
}
},
ValidArgsFunction: suggestVmNames,
Expand Down
2 changes: 2 additions & 0 deletions doc/provisioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ It can also be applied to one or multiple already running VMs:
$ virter vm exec -p provisioning.toml centos-1 centos-2 centos-3
```

If a container or shell provisioning step fails, the virter process will exit with the same exit code as the provisioning script.

## Provisioning types

The following provisioning types are supported.
Expand Down
12 changes: 11 additions & 1 deletion internal/virter/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ const (
colorReset = "\u001b[0m"
)

type ContainerExitError struct {
Status int
}

func (e *ContainerExitError) Error() string {
return fmt.Sprintf("container exited with status %d", e.Status)
}

func containerRun(ctx context.Context, containerProvider containerapi.ContainerProvider, containerCfg *containerapi.ContainerConfig, vmNames []string, vmSSHUserNames []string, vmIPs []string, keyStore sshkeys.KeyStore, knownHosts sshkeys.KnownHosts, copyStep *ProvisionContainerCopyStep) error {
// This is roughly equivalent to
// docker run --rm --network=host -e TARGETS=$vmIPs -e SSH_PRIVATE_KEY="$sshPrivateKey" $dockerImageName
Expand Down Expand Up @@ -192,13 +200,15 @@ func logLines(wg *sync.WaitGroup, vm string, stderr bool, r io.Reader) {
}
}

// containerWait waits for a container to exit.
// If the container exits with a non-zero exit code, a ContainerExitError is returned.
func containerWait(statusCh <-chan int64, errCh <-chan error) error {
select {
case err := <-errCh:
return fmt.Errorf("error waiting for container: %w", err)
case status := <-statusCh:
if status != 0 {
return fmt.Errorf("container returned non-zero exit code %d", status)
return &ContainerExitError{Status: int(status)}
}
return nil
}
Expand Down

0 comments on commit bbe9075

Please sign in to comment.