asteris-llc/converge

View on GitHub
resource/shell/command_results.go

Summary

Maintainability
A
40 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 shell

import (
    "fmt"
    "os"
    "strings"
)

// CommandResults hold the resulting state of command execution
type CommandResults struct {
    ResultsContext

    // the exit status of the process
    ExitStatus uint32 `export:"exitstatus"`

    // the stdout data from the process
    Stdout string `export:"stdout"`

    // the stderr data from the process
    Stderr string `export:"stderr"`

    // the stdin data sent to the process
    Stdin string `export:"stdin"`

    State *os.ProcessState
}

// ResultsContext provides a linked list of CommandResults with a operation
// context that tells us the providence of the command.
type ResultsContext struct {
    Operation string
    Next      *CommandResults
    Prev      *CommandResults
}

// OutputMap returns a map of fds by name and the output on them
func (c *CommandResults) OutputMap() map[string]string {
    m := make(map[string]string)
    if c == nil {
        return m
    }
    if c.Stdout != "" {
        m["stdout"] = c.Stdout
    }
    if c.Stderr != "" {
        m["stderr"] = c.Stderr
    }
    return m
}

// Cons a command result to another command result, allowing the capture of
// multiple runs of a task with a session.
func (c *CommandResults) Cons(op string, toAppend *CommandResults) *CommandResults {
    if c == nil {
        toAppend.Operation = op
        return toAppend
    }
    if toAppend == nil {
        return c
    }
    toAppend.ResultsContext = ResultsContext{Operation: op, Next: c}
    c.Prev = toAppend
    return toAppend
}

// Append adds an element to the end of the list
func (c *CommandResults) Append(op string, toAppend *CommandResults) *CommandResults {
    if toAppend == nil {
        return c
    }
    last := c.Last()
    toAppend.ResultsContext.Prev = last
    toAppend.ResultsContext.Operation = op
    last.Next = toAppend
    return c
}

// Unlink removes an element from the results list, and returns a tuple of the
// removed element and the updated list.  If the Element to remove is the head
// of the list, then it will return the next element, otherwise return the
// current head.
func (c *CommandResults) Unlink(cmd *CommandResults) (removed *CommandResults, results *CommandResults) {
    results = c
    removed = cmd
    if cmd == nil {
        return
    }
    if cmd == c {
        results = c.ResultsContext.Next
    }
    prev := cmd.ResultsContext.Prev
    next := cmd.ResultsContext.Next
    if prev != nil {
        prev.ResultsContext.Next = next
    }
    if next != nil {
        next.ResultsContext.Prev = prev
    }
    return
}

// UnlinkWhen removes each element in the list when the provided function is true
func (c *CommandResults) UnlinkWhen(f func(*CommandResults) bool) *CommandResults {
    if c == nil {
        return c
    }
    cur := c
    for cur != nil {
        if f(cur) {
            _, c = c.Unlink(cur)
        }
        cur = cur.ResultsContext.Next
    }
    return c.First()
}

// Uniq removes duplicate entries (as dicated by Eq) from the results list
func (c *CommandResults) Uniq() *CommandResults {
    for cur := c; cur.ResultsContext.Next != nil; cur = cur.ResultsContext.Next {
        c = cur.ResultsContext.Next.UnlinkWhen(cur.Eq)
    }
    return c
}

// UniqOp removes duplicate operation entries based just on their operation name
func (c *CommandResults) UniqOp() *CommandResults {
    for cur := c; cur.ResultsContext.Next != nil; cur = cur.ResultsContext.Next {
        c = cur.ResultsContext.Next.UnlinkWhen(c.OpEq)
    }
    return c
}

// Summarize provides an overall summary of the results of the command
func (c *CommandResults) Summarize() string {
    if c == nil {
        return ""
    }
    return fmt.Sprintf(
        "%s (returned: %d)\n\t%s\n\t%s",
        c.ResultsContext.Operation,
        c.ExitStatus,
        c.Stdout,
        c.Stderr,
    )
}

// SummarizeAll returnes a list of summaries of a command result and it's
// ancestors
func (c *CommandResults) SummarizeAll() (summaries []string) {
    for c != nil {
        summaries = append(summaries, c.Summarize())
        c = c.ResultsContext.Next
    }
    return
}

// Eq tests command results for equality
func (c *CommandResults) Eq(cmd *CommandResults) bool {
    if c == nil || cmd == nil {
        return false
    }
    if c.ExitStatus != cmd.ExitStatus {
        return false
    }
    if c.Stdout != cmd.Stdout {
        return false
    }
    if c.Stderr != cmd.Stderr {
        return false
    }
    if c.ResultsContext.Operation != cmd.ResultsContext.Operation {
        return false
    }
    return true
}

// OpEq tests command results for equality using only the operation name
func (c *CommandResults) OpEq(cmd *CommandResults) bool {
    if c == nil || cmd == nil {
        return false
    }
    return c.ResultsContext.Operation == cmd.ResultsContext.Operation
}

// ExitStatuses returns a slice with the exit status of all the commands
func (c *CommandResults) ExitStatuses() (results []uint32) {
    for c != nil {
        results = append(results, c.ExitStatus)
        c = c.ResultsContext.Next
    }
    return
}

// ExitStrings returns a list of strings containing the operation type and exit
// status in the form of Operation (code).
func (c *CommandResults) ExitStrings() (results []string) {
    for c != nil {
        results = append(results, fmt.Sprintf("%s (%d)", c.ResultsContext.Operation, c.ExitStatus))
        c = c.ResultsContext.Next
    }
    return
}

// GetMessages will return a set of output messages from all result sets
func (c *CommandResults) GetMessages() (output []string) {
    for c != nil {
        output = append(output, "in "+c.ResultsContext.Operation+":")
        stdout := strings.TrimRight(c.Stdout, "\r\n\t ")
        stderr := strings.TrimRight(c.Stderr, "\r\n\t ")
        if stdout != "" {
            output = append(output, fmt.Sprintf("stdout: %s", stdout))
        }
        if stderr != "" {
            output = append(output, fmt.Sprintf("stderr: %s", stderr))
        }
        c = c.ResultsContext.Next
    }
    return
}

// Last returns the last element in the list
func (c *CommandResults) Last() *CommandResults {
    if c == nil {
        return nil
    }
    last := c
    for last.ResultsContext.Next != nil {
        last = last.ResultsContext.Next
    }
    return last
}

// First returns the head of the results list
func (c *CommandResults) First() *CommandResults {
    if c == nil {
        return nil
    }
    first := c
    for first.ResultsContext.Prev != nil {
        first = first.ResultsContext.Prev
    }
    return first
}

// reverseNode reverses the direction of a single node
func (c *CommandResults) reverseNode() *CommandResults {
    if c == nil {
        return c
    }
    prev := c.ResultsContext.Prev
    next := c.ResultsContext.Next
    c.ResultsContext.Prev = next
    c.ResultsContext.Next = prev
    return c
}

// Reverse will reverse the list
func (c *CommandResults) Reverse() *CommandResults {
    last := c.Last()
    for cur := last; cur != nil; cur = cur.ResultsContext.Next {
        cur = cur.reverseNode()
    }
    return last
}