asteris-llc/converge

View on GitHub
resource/lvm/lowlevel/exec.go

Summary

Maintainability
A
0 mins
Test Coverage
// Copyright © 2016 Asteris, LLC
//
// 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 lowlevel

import (
    "fmt"
    "io/ioutil"
    "os"
    "os/exec"
    "path/filepath"
    "strings"
    "syscall"

    log "github.com/Sirupsen/logrus"
    "github.com/pkg/errors"
)

// Exec is interface to `real system` also used for test injections
// NB: should be reviewed, and possible refactored as base for all
//     processes/filesystem operrations, to make the easy mockable
// NB: possoble split to ExecInterface, FilesystemInterface (possible also SystemInterface for Getuid)
// Related issue: https://github.com/asteris-llc/converge/issues/455
type Exec interface {
    Run(prog string, args []string) error
    RunWithExitCode(prog string, args []string) (int, error) // for mountpoint querying
    Read(prog string, args []string) (stdout string, err error)
    ReadWithExitCode(prog string, args []string) (stdout string, rc int, err error) // for blkid querying
    Lookup(prog string) error
    Getuid() int

    // unit read/write injection
    ReadFile(fn string) ([]byte, error)
    WriteFile(fn string, c []byte, p os.FileMode) error
    MkdirAll(path string, perm os.FileMode) error
    Exists(path string) (bool, error)

    // Local Filesystem Functions
    EvalSymlinks(string) (string, error)
}

type osExec struct {
}

// MakeOsExec create Exec backend
func MakeOsExec() Exec {
    return &osExec{}
}

func (*osExec) EvalSymlinks(path string) (string, error) {
    return filepath.EvalSymlinks(path)
}

func (*osExec) Run(prog string, args []string) error {
    log.WithField("module", "lvm").Infof("Executing %s: %v", prog, args)
    e := exec.Command(prog, args...).Run()
    if e == nil {
        log.WithField("module", "lvm").Debugf("%s: no error", prog)
    } else {
        log.WithField("module", "lvm").Debugf("%s: terminated with %s", prog, e.Error())
    }
    return e
}

func (e *osExec) RunWithExitCode(prog string, args []string) (int, error) {
    err := e.Run(prog, args)
    return exitStatus(err)
}

func (*osExec) ReadWithExitCode(prog string, args []string) (stdout string, rc int, err error) {
    rc = 0
    log.WithField("module", "lvm").Infof("Executing (read) %s: %v", prog, args)
    out, err := exec.Command(prog, args...).Output()
    strOut := strings.Trim(string(out), "\n ")
    if err != nil {
        rc, err = exitStatus(err)
        if err != nil {
            log.WithField("module", "lvm").Debugf("%s: terminated with %s", prog, err.Error())
            return "", 0, errors.Wrapf(err, "reading output of process %s: %s", prog, args)
        }
    }
    return strOut, rc, err
}

func (e *osExec) Read(prog string, args []string) (stdout string, err error) {
    out, rc, err := e.ReadWithExitCode(prog, args)
    if err != nil {
        return "", err
    }
    if rc != 0 {
        return "", fmt.Errorf("process %s: %s terminated with status code %d", prog, args, rc)
    }
    return out, nil
}

func exitStatus(err error) (int, error) {
    if exiterr, ok := err.(*exec.ExitError); ok {
        // The program has exited with an exit code != 0
        // This works on both Unix and Windows. Although package
        // syscall is generally platform dependent, WaitStatus is
        // defined for both Unix and Windows and in both cases has
        // an ExitStatus() method with the same signature.
        if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
            return status.ExitStatus(), nil
        }
    }
    return 0, err
}

func (*osExec) Lookup(prog string) error {
    _, err := exec.LookPath(prog)
    return err
}

func (*osExec) ReadFile(fn string) ([]byte, error) {
    log.WithField("module", "lvm").Debugf("Reading %s...", fn)
    return ioutil.ReadFile(fn)
}

func (*osExec) WriteFile(fn string, content []byte, perm os.FileMode) error {
    log.WithField("module", "lvm").Debugf("Writing %s...", fn)
    return ioutil.WriteFile(fn, content, perm)
}

func (*osExec) MkdirAll(path string, perm os.FileMode) error {
    log.WithField("module", "lvm").Debugf("Make path %s...", path)
    return os.MkdirAll(path, perm)
}

func (*osExec) Exists(path string) (bool, error) {
    if _, err := os.Stat(path); err != nil {
        if os.IsNotExist(err) {
            return false, nil
        }
        return false, err
    }
    return true, nil
}

func (*osExec) Getuid() int {
    return os.Getuid()
}