Add argo as a subcommand
This is a drop-in of the argo cmd package, with some slight tweaks to adhere to airshipctl's models and linter. It still needs to be combed over and modified to meet airshipctl's needs, as well as requiring unit tests
This commit is contained in:
parent
3c02f845d0
commit
5999d17323
117
cmd/argo/common.go
Normal file
117
cmd/argo/common.go
Normal file
@ -0,0 +1,117 @@
|
||||
package argo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
wfv1 "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1"
|
||||
wfclientset "github.com/argoproj/argo/pkg/client/clientset/versioned"
|
||||
"github.com/argoproj/argo/pkg/client/clientset/versioned/typed/workflow/v1alpha1"
|
||||
)
|
||||
|
||||
// Global variables
|
||||
var (
|
||||
restConfig *rest.Config
|
||||
clientConfig clientcmd.ClientConfig
|
||||
clientset *kubernetes.Clientset
|
||||
wfClient v1alpha1.WorkflowInterface
|
||||
jobStatusIconMap map[wfv1.NodePhase]string
|
||||
noColor bool
|
||||
namespace string
|
||||
)
|
||||
|
||||
func init() {
|
||||
cobra.OnInitialize(initializeSession)
|
||||
}
|
||||
|
||||
// ANSI escape codes
|
||||
const (
|
||||
escape = "\x1b"
|
||||
noFormat = 0
|
||||
Bold = 1
|
||||
FgBlack = 30
|
||||
FgRed = 31
|
||||
FgGreen = 32
|
||||
FgYellow = 33
|
||||
FgBlue = 34
|
||||
FgMagenta = 35
|
||||
FgCyan = 36
|
||||
FgWhite = 37
|
||||
FgDefault = 39
|
||||
)
|
||||
|
||||
func initializeSession() {
|
||||
jobStatusIconMap = map[wfv1.NodePhase]string{
|
||||
wfv1.NodePending: ansiFormat("◷", FgYellow),
|
||||
wfv1.NodeRunning: ansiFormat("●", FgCyan),
|
||||
wfv1.NodeSucceeded: ansiFormat("✔", FgGreen),
|
||||
wfv1.NodeSkipped: ansiFormat("○", FgDefault),
|
||||
wfv1.NodeFailed: ansiFormat("✖", FgRed),
|
||||
wfv1.NodeError: ansiFormat("⚠", FgRed),
|
||||
}
|
||||
}
|
||||
|
||||
func initKubeClient() *kubernetes.Clientset {
|
||||
if clientset != nil {
|
||||
return clientset
|
||||
}
|
||||
var err error
|
||||
restConfig, err = clientConfig.ClientConfig()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// create the clientset
|
||||
clientset, err = kubernetes.NewForConfig(restConfig)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return clientset
|
||||
}
|
||||
|
||||
// InitWorkflowClient creates a new client for the Kubernetes Workflow CRD.
|
||||
func InitWorkflowClient(ns ...string) v1alpha1.WorkflowInterface {
|
||||
if wfClient != nil && (len(ns) == 0 || ns[0] == namespace) {
|
||||
return wfClient
|
||||
}
|
||||
initKubeClient()
|
||||
var err error
|
||||
if len(ns) > 0 {
|
||||
namespace = ns[0]
|
||||
} else {
|
||||
namespace, _, err = clientConfig.Namespace()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
wfcs := wfclientset.NewForConfigOrDie(restConfig)
|
||||
wfClient = wfcs.ArgoprojV1alpha1().Workflows(namespace)
|
||||
return wfClient
|
||||
}
|
||||
|
||||
// ansiFormat wraps ANSI escape codes to a string to format the string to a desired color.
|
||||
// NOTE: we still apply formatting even if there is no color formatting desired.
|
||||
// The purpose of doing this is because when we apply ANSI color escape sequences to our
|
||||
// output, this confuses the tabwriter library which miscalculates widths of columns and
|
||||
// misaligns columns. By always applying a ANSI escape sequence (even when we don't want
|
||||
// color, it provides more consistent string lengths so that tabwriter can calculate
|
||||
// widths correctly.
|
||||
func ansiFormat(s string, codes ...int) string {
|
||||
if noColor || os.Getenv("TERM") == "dumb" || len(codes) == 0 {
|
||||
return s
|
||||
}
|
||||
codeStrs := make([]string, len(codes))
|
||||
for i, code := range codes {
|
||||
codeStrs[i] = strconv.Itoa(code)
|
||||
}
|
||||
sequence := strings.Join(codeStrs, ";")
|
||||
return fmt.Sprintf("%s[%sm%s%s[%dm", escape, sequence, s, escape, noFormat)
|
||||
}
|
84
cmd/argo/delete.go
Normal file
84
cmd/argo/delete.go
Normal file
@ -0,0 +1,84 @@
|
||||
package argo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/argoproj/argo/workflow/common"
|
||||
argotime "github.com/argoproj/pkg/time"
|
||||
)
|
||||
|
||||
var (
|
||||
completedWorkflowListOption = metav1.ListOptions{
|
||||
LabelSelector: fmt.Sprintf("%s=true", common.LabelKeyCompleted),
|
||||
}
|
||||
)
|
||||
|
||||
// NewDeleteCommand returns a new instance of an `argo delete` command
|
||||
func NewDeleteCommand() *cobra.Command {
|
||||
var (
|
||||
all bool
|
||||
completed bool
|
||||
older string
|
||||
)
|
||||
|
||||
var command = &cobra.Command{
|
||||
Use: "delete WORKFLOW",
|
||||
Short: "delete a workflow and its associated pods",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
wfClient = InitWorkflowClient()
|
||||
if all {
|
||||
deleteWorkflows(metav1.ListOptions{}, nil)
|
||||
} else if older != "" {
|
||||
olderTime, err := argotime.ParseSince(older)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
deleteWorkflows(completedWorkflowListOption, olderTime)
|
||||
} else if completed {
|
||||
deleteWorkflows(completedWorkflowListOption, nil)
|
||||
} else {
|
||||
if len(args) == 0 {
|
||||
cmd.HelpFunc()(cmd, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
for _, wfName := range args {
|
||||
deleteWorkflow(wfName)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
command.Flags().BoolVar(&all, "all", false, "Delete all workflows")
|
||||
command.Flags().BoolVar(&completed, "completed", false, "Delete completed workflows")
|
||||
command.Flags().StringVar(&older, "older", "", "Delete completed workflows older than the specified duration (e.g. 10m, 3h, 1d)")
|
||||
return command
|
||||
}
|
||||
|
||||
func deleteWorkflow(wfName string) {
|
||||
err := wfClient.Delete(wfName, &metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("Workflow '%s' deleted\n", wfName)
|
||||
}
|
||||
|
||||
func deleteWorkflows(options metav1.ListOptions, older *time.Time) {
|
||||
wfList, err := wfClient.List(options)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, wf := range wfList.Items {
|
||||
if older != nil {
|
||||
if wf.Status.FinishedAt.IsZero() || wf.Status.FinishedAt.After(*older) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
deleteWorkflow(wf.ObjectMeta.Name)
|
||||
}
|
||||
}
|
488
cmd/argo/get.go
Normal file
488
cmd/argo/get.go
Normal file
@ -0,0 +1,488 @@
|
||||
package argo
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
wfv1 "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1"
|
||||
"github.com/argoproj/argo/workflow/util"
|
||||
"github.com/argoproj/pkg/humanize"
|
||||
|
||||
env "github.com/ian-howell/airshipctl/pkg/environment"
|
||||
)
|
||||
|
||||
const onExitSuffix = "onExit"
|
||||
|
||||
type getFlags struct {
|
||||
output string
|
||||
status string
|
||||
}
|
||||
|
||||
func NewGetCommand() *cobra.Command {
|
||||
var (
|
||||
getArgs getFlags
|
||||
)
|
||||
|
||||
var command = &cobra.Command{
|
||||
Use: "get WORKFLOW",
|
||||
Short: "display details about a workflow",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
cmd.HelpFunc()(cmd, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
wfClient := InitWorkflowClient()
|
||||
wf, err := wfClient.Get(args[0], metav1.GetOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = util.DecompressWorkflow(wf)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
printWorkflow(wf, getArgs.output, getArgs.status)
|
||||
},
|
||||
}
|
||||
|
||||
command.Flags().StringVarP(&getArgs.output, "output", "o", "", "Output format. One of: json|yaml|wide")
|
||||
command.Flags().BoolVar(&noColor, "no-color", false, "Disable colorized output")
|
||||
command.Flags().StringVar(&getArgs.status, "status", "", "Filter by status (Pending, Running, Succeeded, Skipped, Failed, Error)")
|
||||
return command
|
||||
}
|
||||
|
||||
func printWorkflow(wf *wfv1.Workflow, output, status string) {
|
||||
getArgs := getFlags{
|
||||
output: output,
|
||||
status: status,
|
||||
}
|
||||
switch getArgs.output {
|
||||
case env.NameOnly:
|
||||
fmt.Println(wf.ObjectMeta.Name)
|
||||
case env.JSON:
|
||||
outBytes, _ := json.MarshalIndent(wf, "", " ")
|
||||
fmt.Println(string(outBytes))
|
||||
case env.YAML:
|
||||
outBytes, _ := yaml.Marshal(wf)
|
||||
fmt.Print(string(outBytes))
|
||||
case env.Wide, env.Default:
|
||||
printWorkflowHelper(wf, getArgs)
|
||||
default:
|
||||
log.Fatalf("Unknown output format: %s", getArgs.output)
|
||||
}
|
||||
}
|
||||
|
||||
func printWorkflowHelper(wf *wfv1.Workflow, getArgs getFlags) {
|
||||
const fmtStr = "%-20s %v\n"
|
||||
fmt.Printf(fmtStr, "Name:", wf.ObjectMeta.Name)
|
||||
fmt.Printf(fmtStr, "Namespace:", wf.ObjectMeta.Namespace)
|
||||
serviceAccount := wf.Spec.ServiceAccountName
|
||||
if serviceAccount == "" {
|
||||
serviceAccount = "default"
|
||||
}
|
||||
fmt.Printf(fmtStr, "ServiceAccount:", serviceAccount)
|
||||
fmt.Printf(fmtStr, "Status:", workflowStatus(wf))
|
||||
if wf.Status.Message != "" {
|
||||
fmt.Printf(fmtStr, "Message:", wf.Status.Message)
|
||||
}
|
||||
fmt.Printf(fmtStr, "Created:", humanize.Timestamp(wf.ObjectMeta.CreationTimestamp.Time))
|
||||
if !wf.Status.StartedAt.IsZero() {
|
||||
fmt.Printf(fmtStr, "Started:", humanize.Timestamp(wf.Status.StartedAt.Time))
|
||||
}
|
||||
if !wf.Status.FinishedAt.IsZero() {
|
||||
fmt.Printf(fmtStr, "Finished:", humanize.Timestamp(wf.Status.FinishedAt.Time))
|
||||
}
|
||||
if !wf.Status.StartedAt.IsZero() {
|
||||
fmt.Printf(fmtStr, "Duration:", humanize.RelativeDuration(wf.Status.StartedAt.Time, wf.Status.FinishedAt.Time))
|
||||
}
|
||||
|
||||
if len(wf.Spec.Arguments.Parameters) > 0 {
|
||||
fmt.Printf(fmtStr, "Parameters:", "")
|
||||
for _, param := range wf.Spec.Arguments.Parameters {
|
||||
if param.Value == nil {
|
||||
continue
|
||||
}
|
||||
fmt.Printf(fmtStr, " "+param.Name+":", *param.Value)
|
||||
}
|
||||
}
|
||||
if wf.Status.Outputs != nil {
|
||||
//fmt.Printf(fmtStr, "Outputs:", "")
|
||||
if len(wf.Status.Outputs.Parameters) > 0 {
|
||||
fmt.Printf(fmtStr, "Output Parameters:", "")
|
||||
for _, param := range wf.Status.Outputs.Parameters {
|
||||
fmt.Printf(fmtStr, " "+param.Name+":", *param.Value)
|
||||
}
|
||||
}
|
||||
if len(wf.Status.Outputs.Artifacts) > 0 {
|
||||
fmt.Printf(fmtStr, "Output Artifacts:", "")
|
||||
for _, art := range wf.Status.Outputs.Artifacts {
|
||||
if art.S3 != nil {
|
||||
fmt.Printf(fmtStr, " "+art.Name+":", art.S3.String())
|
||||
} else if art.Artifactory != nil {
|
||||
fmt.Printf(fmtStr, " "+art.Name+":", art.Artifactory.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
printTree := true
|
||||
if wf.Status.Nodes == nil {
|
||||
printTree = false
|
||||
} else if _, ok := wf.Status.Nodes[wf.ObjectMeta.Name]; !ok {
|
||||
printTree = false
|
||||
}
|
||||
if printTree {
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
fmt.Println()
|
||||
// apply a dummy FgDefault format to align tabwriter with the rest of the columns
|
||||
if getArgs.output == env.Wide {
|
||||
fmt.Fprintf(w, "%s\tPODNAME\tDURATION\tARTIFACTS\tMESSAGE\n", ansiFormat("STEP", FgDefault))
|
||||
} else {
|
||||
fmt.Fprintf(w, "%s\tPODNAME\tDURATION\tMESSAGE\n", ansiFormat("STEP", FgDefault))
|
||||
}
|
||||
|
||||
// Convert Nodes to Render Trees
|
||||
roots := convertToRenderTrees(wf)
|
||||
|
||||
// Print main and onExit Trees
|
||||
mainRoot := roots[wf.ObjectMeta.Name]
|
||||
mainRoot.renderNodes(w, wf, 0, " ", " ", getArgs)
|
||||
|
||||
onExitID := wf.NodeID(wf.ObjectMeta.Name + "." + onExitSuffix)
|
||||
if onExitRoot, ok := roots[onExitID]; ok {
|
||||
fmt.Fprintf(w, "\t\t\t\t\t\n")
|
||||
onExitRoot.renderNodes(w, wf, 0, " ", " ", getArgs)
|
||||
}
|
||||
_ = w.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
type nodeInfoInterface interface {
|
||||
getID() string
|
||||
getNodeStatus(wf *wfv1.Workflow) wfv1.NodeStatus
|
||||
getStartTime(wf *wfv1.Workflow) metav1.Time
|
||||
}
|
||||
|
||||
type nodeInfo struct {
|
||||
id string
|
||||
}
|
||||
|
||||
func (n *nodeInfo) getID() string {
|
||||
return n.id
|
||||
}
|
||||
|
||||
func (n *nodeInfo) getNodeStatus(wf *wfv1.Workflow) wfv1.NodeStatus {
|
||||
return wf.Status.Nodes[n.id]
|
||||
}
|
||||
|
||||
func (n *nodeInfo) getStartTime(wf *wfv1.Workflow) metav1.Time {
|
||||
return wf.Status.Nodes[n.id].StartedAt
|
||||
}
|
||||
|
||||
// Interface to represent Nodes in render form types
|
||||
type renderNode interface {
|
||||
// Render this renderNode and its children
|
||||
renderNodes(w *tabwriter.Writer, wf *wfv1.Workflow, depth int, nodePrefix string, childPrefix string, getArgs getFlags)
|
||||
nodeInfoInterface
|
||||
}
|
||||
|
||||
// Currently this is Pod or Resource Nodes
|
||||
type executionNode struct {
|
||||
nodeInfo
|
||||
}
|
||||
|
||||
// Currently this is the step groups or retry nodes
|
||||
type nonBoundaryParentNode struct {
|
||||
nodeInfo
|
||||
children []renderNode // Can be boundaryNode or executionNode
|
||||
}
|
||||
|
||||
// Currently this is the virtual Template node
|
||||
type boundaryNode struct {
|
||||
nodeInfo
|
||||
boundaryContained []renderNode // Can be nonBoundaryParent or executionNode or boundaryNode
|
||||
}
|
||||
|
||||
func isBoundaryNode(node wfv1.NodeType) bool {
|
||||
return (node == wfv1.NodeTypeDAG) || (node == wfv1.NodeTypeSteps)
|
||||
}
|
||||
|
||||
func isNonBoundaryParentNode(node wfv1.NodeType) bool {
|
||||
return (node == wfv1.NodeTypeStepGroup) || (node == wfv1.NodeTypeRetry)
|
||||
}
|
||||
|
||||
func isExecutionNode(node wfv1.NodeType) bool {
|
||||
return (node == wfv1.NodeTypePod) || (node == wfv1.NodeTypeSkipped) || (node == wfv1.NodeTypeSuspend)
|
||||
}
|
||||
|
||||
func insertSorted(wf *wfv1.Workflow, sortedArray []renderNode, item renderNode) []renderNode {
|
||||
insertTime := item.getStartTime(wf)
|
||||
var index int
|
||||
for index = 0; index < len(sortedArray); index++ {
|
||||
existingItem := sortedArray[index]
|
||||
t := existingItem.getStartTime(wf)
|
||||
if insertTime.Before(&t) {
|
||||
break
|
||||
} else if insertTime.Equal(&t) {
|
||||
// If they are equal apply alphabetical order so we
|
||||
// get some consistent printing
|
||||
insertName := item.getNodeStatus(wf).DisplayName
|
||||
equalName := existingItem.getNodeStatus(wf).DisplayName
|
||||
if insertName < equalName {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
sortedArray = append(sortedArray, nil)
|
||||
copy(sortedArray[index+1:], sortedArray[index:])
|
||||
sortedArray[index] = item
|
||||
return sortedArray
|
||||
}
|
||||
|
||||
// Attach render node n to its parent based on what has been parsed previously
|
||||
// In some cases add it to list of things that still needs to be attached to parent
|
||||
// Return if I am a possible root
|
||||
func attachToParent(wf *wfv1.Workflow, n renderNode,
|
||||
nonBoundaryParentChildrenMap map[string]*nonBoundaryParentNode, boundaryID string,
|
||||
boundaryNodeMap map[string]*boundaryNode, parentBoundaryMap map[string][]renderNode) bool {
|
||||
|
||||
// Check first if I am a child of a nonBoundaryParent
|
||||
// that implies I attach to that instead of my boundary. This was already
|
||||
// figured out in Pass 1
|
||||
if nonBoundaryParent, ok := nonBoundaryParentChildrenMap[n.getID()]; ok {
|
||||
nonBoundaryParent.children = insertSorted(wf, nonBoundaryParent.children, n)
|
||||
return false
|
||||
}
|
||||
|
||||
// If I am not attached to a nonBoundaryParent and I have no Boundary ID then
|
||||
// I am a possible root
|
||||
if boundaryID == "" {
|
||||
return true
|
||||
}
|
||||
if parentBoundary, ok := boundaryNodeMap[boundaryID]; ok {
|
||||
parentBoundary.boundaryContained = insertSorted(wf, parentBoundary.boundaryContained, n)
|
||||
} else {
|
||||
// put ourselves to be added by the parent when we get to it later
|
||||
if _, ok := parentBoundaryMap[boundaryID]; !ok {
|
||||
parentBoundaryMap[boundaryID] = make([]renderNode, 0)
|
||||
}
|
||||
parentBoundaryMap[boundaryID] = append(parentBoundaryMap[boundaryID], n)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// This takes the map of NodeStatus and converts them into a forrest
|
||||
// of trees of renderNodes and returns the set of roots for each tree
|
||||
func convertToRenderTrees(wf *wfv1.Workflow) map[string]renderNode {
|
||||
|
||||
renderTreeRoots := make(map[string]renderNode)
|
||||
|
||||
// Used to store all boundary nodes so future render children can attach
|
||||
// Maps node Name -> *boundaryNode
|
||||
boundaryNodeMap := make(map[string]*boundaryNode)
|
||||
// Used to store children of a boundary node that has not been parsed yet
|
||||
// Maps boundary Node name -> array of render Children
|
||||
parentBoundaryMap := make(map[string][]renderNode)
|
||||
|
||||
// Used to store Non Boundary Parent nodes so render children can attach
|
||||
// Maps non Boundary Parent Node name -> *nonBoundaryParentNode
|
||||
nonBoundaryParentMap := make(map[string]*nonBoundaryParentNode)
|
||||
// Used to store children which have a Non Boundary Parent from rendering perspective
|
||||
// Maps non Boundary render Children name -> *nonBoundaryParentNode
|
||||
nonBoundaryParentChildrenMap := make(map[string]*nonBoundaryParentNode)
|
||||
|
||||
// We have to do a 2 pass approach because anything that is a child
|
||||
// of a nonBoundaryParent and also has a boundaryID we may not know which
|
||||
// parent to attach to if we didn't see the nonBoundaryParent earlier
|
||||
// in a 1 pass strategy
|
||||
|
||||
// 1st Pass Process enough of nonBoundaryParent nodes to know all their children
|
||||
for id, status := range wf.Status.Nodes {
|
||||
if status.Type == "" {
|
||||
log.Fatal("Missing node type in status node. Cannot get workflows created with Argo <= 2.0 using the default or wide output option.")
|
||||
return nil
|
||||
}
|
||||
if isNonBoundaryParentNode(status.Type) {
|
||||
n := nonBoundaryParentNode{nodeInfo: nodeInfo{id: id}}
|
||||
nonBoundaryParentMap[id] = &n
|
||||
|
||||
for _, child := range status.Children {
|
||||
nonBoundaryParentChildrenMap[child] = &n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2nd Pass process everything
|
||||
for id, status := range wf.Status.Nodes {
|
||||
switch {
|
||||
case isBoundaryNode(status.Type):
|
||||
n := boundaryNode{nodeInfo: nodeInfo{id: id}}
|
||||
boundaryNodeMap[id] = &n
|
||||
// Attach to my parent if needed
|
||||
if attachToParent(wf, &n, nonBoundaryParentChildrenMap,
|
||||
status.BoundaryID, boundaryNodeMap, parentBoundaryMap) {
|
||||
renderTreeRoots[n.getID()] = &n
|
||||
}
|
||||
// Attach nodes who are in my boundary already seen before me to me
|
||||
for _, val := range parentBoundaryMap[id] {
|
||||
n.boundaryContained = insertSorted(wf, n.boundaryContained, val)
|
||||
}
|
||||
case isNonBoundaryParentNode(status.Type):
|
||||
nPtr, ok := nonBoundaryParentMap[id]
|
||||
if !ok {
|
||||
log.Fatal("Unable to lookup node " + id)
|
||||
return nil
|
||||
}
|
||||
// Attach to my parent if needed
|
||||
if attachToParent(wf, nPtr, nonBoundaryParentChildrenMap,
|
||||
status.BoundaryID, boundaryNodeMap, parentBoundaryMap) {
|
||||
renderTreeRoots[nPtr.getID()] = nPtr
|
||||
}
|
||||
// All children attach directly to the nonBoundaryParents since they are already created
|
||||
// in pass 1 so no need to do that here
|
||||
case isExecutionNode(status.Type):
|
||||
n := executionNode{nodeInfo: nodeInfo{id: id}}
|
||||
// Attach to my parent if needed
|
||||
if attachToParent(wf, &n, nonBoundaryParentChildrenMap,
|
||||
status.BoundaryID, boundaryNodeMap, parentBoundaryMap) {
|
||||
renderTreeRoots[n.getID()] = &n
|
||||
}
|
||||
// Execution nodes don't have other render nodes as children
|
||||
}
|
||||
}
|
||||
|
||||
return renderTreeRoots
|
||||
}
|
||||
|
||||
// This function decides if a Node will be filtered from rendering and returns
|
||||
// two things. First argument tells if the node is filtered and second argument
|
||||
// tells whether the children need special indentation due to filtering
|
||||
// Return Values: (is node filtered, do children need special indent)
|
||||
func filterNode(node wfv1.NodeStatus) (bool, bool) {
|
||||
if node.Type == wfv1.NodeTypeRetry && len(node.Children) == 1 {
|
||||
return true, false
|
||||
} else if node.Type == wfv1.NodeTypeStepGroup {
|
||||
return true, true
|
||||
}
|
||||
return false, false
|
||||
}
|
||||
|
||||
// Render the child of a given node based on information about the parent such as:
|
||||
// whether it was filtered and does this child need special indent
|
||||
func renderChild(w *tabwriter.Writer, wf *wfv1.Workflow, nInfo renderNode, depth int,
|
||||
nodePrefix string, childPrefix string, parentFiltered bool,
|
||||
childIndex int, maxIndex int, childIndent bool, getArgs getFlags) {
|
||||
var part, subp string
|
||||
if parentFiltered && childIndent {
|
||||
if maxIndex == 0 {
|
||||
part = "--"
|
||||
subp = " "
|
||||
} else if childIndex == 0 {
|
||||
part = "·-"
|
||||
subp = "| "
|
||||
} else if childIndex == maxIndex {
|
||||
part = "└-"
|
||||
subp = " "
|
||||
} else {
|
||||
part = "├-"
|
||||
subp = "| "
|
||||
}
|
||||
} else if !parentFiltered {
|
||||
if childIndex == maxIndex {
|
||||
part = "└-"
|
||||
subp = " "
|
||||
} else {
|
||||
part = "├-"
|
||||
subp = "| "
|
||||
}
|
||||
}
|
||||
var childNodePrefix, childChldPrefix string
|
||||
if !parentFiltered {
|
||||
depth = depth + 1
|
||||
childNodePrefix = childPrefix + part
|
||||
childChldPrefix = childPrefix + subp
|
||||
} else {
|
||||
if childIndex == 0 {
|
||||
childNodePrefix = nodePrefix + part
|
||||
} else {
|
||||
childNodePrefix = childPrefix + part
|
||||
}
|
||||
childChldPrefix = childPrefix + subp
|
||||
}
|
||||
nInfo.renderNodes(w, wf, depth, childNodePrefix, childChldPrefix, getArgs)
|
||||
}
|
||||
|
||||
// Main method to print information of node in get
|
||||
func printNode(w *tabwriter.Writer, node wfv1.NodeStatus, nodePrefix string, getArgs getFlags) {
|
||||
if getArgs.status != "" && string(node.Phase) != getArgs.status {
|
||||
return
|
||||
}
|
||||
nodeName := fmt.Sprintf("%s %s", jobStatusIconMap[node.Phase], node.DisplayName)
|
||||
var args []interface{}
|
||||
duration := humanize.RelativeDurationShort(node.StartedAt.Time, node.FinishedAt.Time)
|
||||
if node.Type == wfv1.NodeTypePod {
|
||||
args = []interface{}{nodePrefix, nodeName, node.ID, duration, node.Message}
|
||||
} else {
|
||||
args = []interface{}{nodePrefix, nodeName, "", "", node.Message}
|
||||
}
|
||||
if getArgs.output == env.Wide {
|
||||
msg := args[len(args)-1]
|
||||
args[len(args)-1] = getArtifactsString(node)
|
||||
args = append(args, msg)
|
||||
fmt.Fprintf(w, "%s%s\t%s\t%s\t%s\t%s\n", args...)
|
||||
} else {
|
||||
fmt.Fprintf(w, "%s%s\t%s\t%s\t%s\n", args...)
|
||||
}
|
||||
}
|
||||
|
||||
// renderNodes for each renderNode Type
|
||||
// boundaryNode
|
||||
func (nodeInfo *boundaryNode) renderNodes(w *tabwriter.Writer, wf *wfv1.Workflow, depth int, nodePrefix string, childPrefix string, getArgs getFlags) {
|
||||
filtered, childIndent := filterNode(nodeInfo.getNodeStatus(wf))
|
||||
if !filtered {
|
||||
printNode(w, nodeInfo.getNodeStatus(wf), nodePrefix, getArgs)
|
||||
}
|
||||
|
||||
for i, nInfo := range nodeInfo.boundaryContained {
|
||||
renderChild(w, wf, nInfo, depth, nodePrefix, childPrefix, filtered, i,
|
||||
len(nodeInfo.boundaryContained)-1, childIndent, getArgs)
|
||||
}
|
||||
}
|
||||
|
||||
// nonBoundaryParentNode
|
||||
func (nodeInfo *nonBoundaryParentNode) renderNodes(w *tabwriter.Writer, wf *wfv1.Workflow, depth int, nodePrefix string, childPrefix string, getArgs getFlags) {
|
||||
filtered, childIndent := filterNode(nodeInfo.getNodeStatus(wf))
|
||||
if !filtered {
|
||||
printNode(w, nodeInfo.getNodeStatus(wf), nodePrefix, getArgs)
|
||||
}
|
||||
|
||||
for i, nInfo := range nodeInfo.children {
|
||||
renderChild(w, wf, nInfo, depth, nodePrefix, childPrefix, filtered, i,
|
||||
len(nodeInfo.children)-1, childIndent, getArgs)
|
||||
}
|
||||
}
|
||||
|
||||
// executionNode
|
||||
func (nodeInfo *executionNode) renderNodes(w *tabwriter.Writer, wf *wfv1.Workflow, depth int, nodePrefix string, childPrefix string, getArgs getFlags) {
|
||||
filtered, _ := filterNode(nodeInfo.getNodeStatus(wf))
|
||||
if !filtered {
|
||||
printNode(w, nodeInfo.getNodeStatus(wf), nodePrefix, getArgs)
|
||||
}
|
||||
}
|
||||
|
||||
func getArtifactsString(node wfv1.NodeStatus) string {
|
||||
if node.Outputs == nil {
|
||||
return ""
|
||||
}
|
||||
artNames := []string{}
|
||||
for _, art := range node.Outputs.Artifacts {
|
||||
artNames = append(artNames, art.Name)
|
||||
}
|
||||
return strings.Join(artNames, ",")
|
||||
}
|
59
cmd/argo/lint.go
Normal file
59
cmd/argo/lint.go
Normal file
@ -0,0 +1,59 @@
|
||||
package argo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
cmdutil "github.com/argoproj/argo/util/cmd"
|
||||
"github.com/argoproj/argo/workflow/validate"
|
||||
)
|
||||
|
||||
func NewLintCommand() *cobra.Command {
|
||||
var (
|
||||
strict bool
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "lint (DIRECTORY | FILE1 FILE2 FILE3...)",
|
||||
Short: "validate a file or directory of workflow manifests",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
cmd.HelpFunc()(cmd, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
validateDir := cmdutil.MustIsDir(args[0])
|
||||
var err error
|
||||
if validateDir {
|
||||
if len(args) > 1 {
|
||||
fmt.Printf("Validation of a single directory supported")
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Verifying all workflow manifests in directory: %s\n", args[0])
|
||||
err = validate.LintWorkflowDir(args[0], strict)
|
||||
} else {
|
||||
yamlFiles := make([]string, 0)
|
||||
for _, filePath := range args {
|
||||
if cmdutil.MustIsDir(filePath) {
|
||||
fmt.Printf("Validate against a list of files or a single directory, not both")
|
||||
os.Exit(1)
|
||||
}
|
||||
yamlFiles = append(yamlFiles, filePath)
|
||||
}
|
||||
for _, yamlFile := range yamlFiles {
|
||||
err = validate.LintWorkflowFile(yamlFile, strict)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("Workflow manifests validated\n")
|
||||
},
|
||||
}
|
||||
command.Flags().BoolVar(&strict, "strict", true, "perform strict workflow validatation")
|
||||
return command
|
||||
}
|
229
cmd/argo/list.go
Normal file
229
cmd/argo/list.go
Normal file
@ -0,0 +1,229 @@
|
||||
package argo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/selection"
|
||||
|
||||
wfv1 "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1"
|
||||
"github.com/argoproj/argo/pkg/client/clientset/versioned/typed/workflow/v1alpha1"
|
||||
"github.com/argoproj/argo/workflow/common"
|
||||
"github.com/argoproj/argo/workflow/util"
|
||||
"github.com/argoproj/pkg/humanize"
|
||||
argotime "github.com/argoproj/pkg/time"
|
||||
|
||||
env "github.com/ian-howell/airshipctl/pkg/environment"
|
||||
)
|
||||
|
||||
type listFlags struct {
|
||||
allNamespaces bool // --all-namespaces
|
||||
status string // --status
|
||||
completed bool // --completed
|
||||
running bool // --running
|
||||
output string // --output
|
||||
since string // --since
|
||||
}
|
||||
|
||||
func NewListCommand() *cobra.Command {
|
||||
var (
|
||||
listArgs listFlags
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "list workflows",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
var wfClient v1alpha1.WorkflowInterface
|
||||
if listArgs.allNamespaces {
|
||||
wfClient = InitWorkflowClient(apiv1.NamespaceAll)
|
||||
} else {
|
||||
wfClient = InitWorkflowClient()
|
||||
}
|
||||
listOpts := metav1.ListOptions{}
|
||||
labelSelector := labels.NewSelector()
|
||||
if listArgs.status != "" {
|
||||
req, _ := labels.NewRequirement(common.LabelKeyPhase, selection.In, strings.Split(listArgs.status, ","))
|
||||
labelSelector = labelSelector.Add(*req)
|
||||
}
|
||||
if listArgs.completed {
|
||||
req, _ := labels.NewRequirement(common.LabelKeyCompleted, selection.Equals, []string{"true"})
|
||||
labelSelector = labelSelector.Add(*req)
|
||||
}
|
||||
if listArgs.running {
|
||||
req, _ := labels.NewRequirement(common.LabelKeyCompleted, selection.NotEquals, []string{"true"})
|
||||
labelSelector = labelSelector.Add(*req)
|
||||
}
|
||||
listOpts.LabelSelector = labelSelector.String()
|
||||
wfList, err := wfClient.List(listOpts)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
var workflows []wfv1.Workflow
|
||||
if listArgs.since == "" {
|
||||
workflows = wfList.Items
|
||||
} else {
|
||||
workflows = make([]wfv1.Workflow, 0)
|
||||
minTime, err := argotime.ParseSince(listArgs.since)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, wf := range wfList.Items {
|
||||
if wf.Status.FinishedAt.IsZero() || wf.ObjectMeta.CreationTimestamp.After(*minTime) {
|
||||
workflows = append(workflows, wf)
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Sort(ByFinishedAt(workflows))
|
||||
|
||||
switch listArgs.output {
|
||||
case env.Default, env.Wide:
|
||||
printTable(workflows, &listArgs)
|
||||
case env.NameOnly:
|
||||
for _, wf := range workflows {
|
||||
fmt.Println(wf.ObjectMeta.Name)
|
||||
}
|
||||
default:
|
||||
log.Fatalf("Unknown output mode: %s", listArgs.output)
|
||||
}
|
||||
},
|
||||
}
|
||||
command.Flags().BoolVar(&listArgs.allNamespaces, "all-namespaces", false, "Show workflows from all namespaces")
|
||||
command.Flags().StringVar(&listArgs.status, "status", "", "Filter by status (comma separated)")
|
||||
command.Flags().BoolVar(&listArgs.completed, "completed", false, "Show only completed workflows")
|
||||
command.Flags().BoolVar(&listArgs.running, "running", false, "Show only running workflows")
|
||||
command.Flags().StringVarP(&listArgs.output, "output", "o", "", "Output format. One of: wide|name")
|
||||
command.Flags().StringVar(&listArgs.since, "since", "", "Show only workflows newer than a relative duration")
|
||||
return command
|
||||
}
|
||||
|
||||
func printTable(wfList []wfv1.Workflow, listArgs *listFlags) {
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
|
||||
if listArgs.allNamespaces {
|
||||
fmt.Fprint(w, "NAMESPACE\t")
|
||||
}
|
||||
fmt.Fprint(w, "NAME\tSTATUS\tAGE\tDURATION\tPRIORITY")
|
||||
if listArgs.output == env.Wide {
|
||||
fmt.Fprint(w, "\tP/R/C\tPARAMETERS")
|
||||
}
|
||||
fmt.Fprint(w, "\n")
|
||||
for _, wf := range wfList {
|
||||
ageStr := humanize.RelativeDurationShort(wf.ObjectMeta.CreationTimestamp.Time, time.Now())
|
||||
durationStr := humanize.RelativeDurationShort(wf.Status.StartedAt.Time, wf.Status.FinishedAt.Time)
|
||||
if listArgs.allNamespaces {
|
||||
fmt.Fprintf(w, "%s\t", wf.ObjectMeta.Namespace)
|
||||
}
|
||||
var priority int
|
||||
if wf.Spec.Priority != nil {
|
||||
priority = int(*wf.Spec.Priority)
|
||||
}
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%d", wf.ObjectMeta.Name, workflowStatus(&wf), ageStr, durationStr, priority)
|
||||
if listArgs.output == env.Wide {
|
||||
pending, running, completed := countPendingRunningCompleted(&wf)
|
||||
fmt.Fprintf(w, "\t%d/%d/%d", pending, running, completed)
|
||||
fmt.Fprintf(w, "\t%s", parameterString(wf.Spec.Arguments.Parameters))
|
||||
}
|
||||
fmt.Fprintf(w, "\n")
|
||||
}
|
||||
_ = w.Flush()
|
||||
}
|
||||
|
||||
func countPendingRunningCompleted(wf *wfv1.Workflow) (int, int, int) {
|
||||
pending := 0
|
||||
running := 0
|
||||
completed := 0
|
||||
err := util.DecompressWorkflow(wf)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, node := range wf.Status.Nodes {
|
||||
tmpl := wf.GetTemplate(node.TemplateName)
|
||||
if tmpl == nil || !tmpl.IsPodType() {
|
||||
continue
|
||||
}
|
||||
if node.Completed() {
|
||||
completed++
|
||||
} else if node.Phase == wfv1.NodeRunning {
|
||||
running++
|
||||
} else {
|
||||
pending++
|
||||
}
|
||||
}
|
||||
return pending, running, completed
|
||||
}
|
||||
|
||||
// parameterString returns a human readable display string of the parameters, truncating if necessary
|
||||
func parameterString(params []wfv1.Parameter) string {
|
||||
truncateString := func(str string, num int) string {
|
||||
bnoden := str
|
||||
if len(str) > num {
|
||||
if num > 3 {
|
||||
num -= 3
|
||||
}
|
||||
bnoden = str[0:num-15] + "..." + str[len(str)-15:]
|
||||
}
|
||||
return bnoden
|
||||
}
|
||||
|
||||
pStrs := make([]string, 0)
|
||||
for _, p := range params {
|
||||
if p.Value != nil {
|
||||
str := fmt.Sprintf("%s=%s", p.Name, truncateString(*p.Value, 50))
|
||||
pStrs = append(pStrs, str)
|
||||
}
|
||||
}
|
||||
return strings.Join(pStrs, ",")
|
||||
}
|
||||
|
||||
// ByFinishedAt is a sort interface which sorts running jobs earlier before considering FinishedAt
|
||||
type ByFinishedAt []wfv1.Workflow
|
||||
|
||||
func (f ByFinishedAt) Len() int { return len(f) }
|
||||
func (f ByFinishedAt) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
|
||||
func (f ByFinishedAt) Less(i, j int) bool {
|
||||
iStart := f[i].ObjectMeta.CreationTimestamp
|
||||
iFinish := f[i].Status.FinishedAt
|
||||
jStart := f[j].ObjectMeta.CreationTimestamp
|
||||
jFinish := f[j].Status.FinishedAt
|
||||
if iFinish.IsZero() && jFinish.IsZero() {
|
||||
return !iStart.Before(&jStart)
|
||||
}
|
||||
if iFinish.IsZero() && !jFinish.IsZero() {
|
||||
return true
|
||||
}
|
||||
if !iFinish.IsZero() && jFinish.IsZero() {
|
||||
return false
|
||||
}
|
||||
return jFinish.Before(&iFinish)
|
||||
}
|
||||
|
||||
// workflowStatus returns a human readable inferred workflow status based on workflow phase and conditions
|
||||
func workflowStatus(wf *wfv1.Workflow) wfv1.NodePhase {
|
||||
switch wf.Status.Phase {
|
||||
case wfv1.NodeRunning:
|
||||
if util.IsWorkflowSuspended(wf) {
|
||||
return "Running (Suspended)"
|
||||
}
|
||||
return wf.Status.Phase
|
||||
case wfv1.NodeFailed:
|
||||
if util.IsWorkflowTerminated(wf) {
|
||||
return "Failed (Terminated)"
|
||||
}
|
||||
return wf.Status.Phase
|
||||
case "", wfv1.NodePending:
|
||||
if !wf.ObjectMeta.CreationTimestamp.IsZero() {
|
||||
return wfv1.NodePending
|
||||
}
|
||||
return "Unknown"
|
||||
default:
|
||||
return wf.Status.Phase
|
||||
}
|
||||
}
|
392
cmd/argo/logs.go
Normal file
392
cmd/argo/logs.go
Normal file
@ -0,0 +1,392 @@
|
||||
package argo
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"math"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
pkgwatch "k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/tools/watch"
|
||||
|
||||
"github.com/argoproj/argo/pkg/apis/workflow/v1alpha1"
|
||||
workflowv1 "github.com/argoproj/argo/pkg/client/clientset/versioned/typed/workflow/v1alpha1"
|
||||
"github.com/argoproj/argo/workflow/util"
|
||||
"github.com/argoproj/pkg/errors"
|
||||
)
|
||||
|
||||
type logEntry struct {
|
||||
displayName string
|
||||
pod string
|
||||
time time.Time
|
||||
line string
|
||||
}
|
||||
|
||||
func NewLogsCommand() *cobra.Command {
|
||||
var (
|
||||
printer logPrinter
|
||||
workflow bool
|
||||
since string
|
||||
sinceTime string
|
||||
tail int64
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "logs POD/WORKFLOW",
|
||||
Short: "view logs of a workflow",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
cmd.HelpFunc()(cmd, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
conf, err := clientConfig.ClientConfig()
|
||||
errors.CheckError(err)
|
||||
printer.kubeClient = kubernetes.NewForConfigOrDie(conf)
|
||||
if tail > 0 {
|
||||
printer.tail = &tail
|
||||
}
|
||||
if sinceTime != "" {
|
||||
parsedTime, err := time.Parse(time.RFC3339, sinceTime)
|
||||
errors.CheckError(err)
|
||||
meta1Time := metav1.NewTime(parsedTime)
|
||||
printer.sinceTime = &meta1Time
|
||||
} else if since != "" {
|
||||
parsedSince, err := strconv.ParseInt(since, 10, 64)
|
||||
errors.CheckError(err)
|
||||
printer.sinceSeconds = &parsedSince
|
||||
}
|
||||
|
||||
if workflow {
|
||||
err = printer.PrintWorkflowLogs(args[0])
|
||||
errors.CheckError(err)
|
||||
} else {
|
||||
err = printer.PrintPodLogs(args[0])
|
||||
errors.CheckError(err)
|
||||
}
|
||||
|
||||
},
|
||||
}
|
||||
command.Flags().StringVarP(&printer.container, "container", "c", "main", "Print the logs of this container")
|
||||
command.Flags().BoolVarP(&workflow, "workflow", "w", false, "Specify that whole workflow logs should be printed")
|
||||
command.Flags().BoolVarP(&printer.follow, "follow", "f", false, "Specify if the logs should be streamed.")
|
||||
command.Flags().StringVar(&since, "since", "", "Only return logs newer than a relative duration like 5s, 2m, or 3h. Defaults to all logs. Only one of since-time / since may be used.")
|
||||
command.Flags().StringVar(&sinceTime, "since-time", "", "Only return logs after a specific date (RFC3339). Defaults to all logs. Only one of since-time / since may be used.")
|
||||
command.Flags().Int64Var(&tail, "tail", -1, "Lines of recent log file to display. Defaults to -1 with no selector, showing all log lines otherwise 10, if a selector is provided.")
|
||||
command.Flags().BoolVar(&printer.timestamps, "timestamps", false, "Include timestamps on each line in the log output")
|
||||
return command
|
||||
}
|
||||
|
||||
type logPrinter struct {
|
||||
container string
|
||||
follow bool
|
||||
sinceSeconds *int64
|
||||
sinceTime *metav1.Time
|
||||
tail *int64
|
||||
timestamps bool
|
||||
kubeClient kubernetes.Interface
|
||||
}
|
||||
|
||||
// PrintWorkflowLogs prints logs for all workflow pods
|
||||
func (p *logPrinter) PrintWorkflowLogs(workflow string) error {
|
||||
wfClient := InitWorkflowClient()
|
||||
wf, err := wfClient.Get(workflow, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
timeByPod := p.printRecentWorkflowLogs(wf)
|
||||
if p.follow {
|
||||
p.printLiveWorkflowLogs(wf.Name, wfClient, timeByPod)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PrintPodLogs prints logs for a single pod
|
||||
func (p *logPrinter) PrintPodLogs(podName string) error {
|
||||
namespace, _, err := clientConfig.Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var logs []logEntry
|
||||
err = p.getPodLogs(context.Background(), "", podName, namespace, p.follow, p.tail, p.sinceSeconds, p.sinceTime, func(entry logEntry) {
|
||||
logs = append(logs, entry)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, entry := range logs {
|
||||
p.printLogEntry(entry)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Prints logs for workflow pod steps and return most recent log timestamp per pod name
|
||||
func (p *logPrinter) printRecentWorkflowLogs(wf *v1alpha1.Workflow) map[string]*time.Time {
|
||||
var podNodes []v1alpha1.NodeStatus
|
||||
err := util.DecompressWorkflow(wf)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
return nil
|
||||
}
|
||||
for _, node := range wf.Status.Nodes {
|
||||
if node.Type == v1alpha1.NodeTypePod && node.Phase != v1alpha1.NodeError {
|
||||
podNodes = append(podNodes, node)
|
||||
}
|
||||
}
|
||||
var logs [][]logEntry
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(podNodes))
|
||||
var mux sync.Mutex
|
||||
|
||||
for i := range podNodes {
|
||||
node := podNodes[i]
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
var podLogs []logEntry
|
||||
err := p.getPodLogs(context.Background(), getDisplayName(node), node.ID, wf.Namespace, false, p.tail, p.sinceSeconds, p.sinceTime, func(entry logEntry) {
|
||||
podLogs = append(podLogs, entry)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
return
|
||||
}
|
||||
|
||||
mux.Lock()
|
||||
logs = append(logs, podLogs)
|
||||
mux.Unlock()
|
||||
}()
|
||||
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
flattenLogs := mergeSorted(logs)
|
||||
|
||||
if p.tail != nil {
|
||||
tail := *p.tail
|
||||
if int64(len(flattenLogs)) < tail {
|
||||
tail = int64(len(flattenLogs))
|
||||
}
|
||||
flattenLogs = flattenLogs[0:tail]
|
||||
}
|
||||
timeByPod := make(map[string]*time.Time)
|
||||
for _, entry := range flattenLogs {
|
||||
p.printLogEntry(entry)
|
||||
timeByPod[entry.pod] = &entry.time
|
||||
}
|
||||
return timeByPod
|
||||
}
|
||||
|
||||
// Prints live logs for workflow pods, starting from time specified in timeByPod name.
|
||||
func (p *logPrinter) printLiveWorkflowLogs(workflowName string, wfClient workflowv1.WorkflowInterface, timeByPod map[string]*time.Time) {
|
||||
logs := make(chan logEntry)
|
||||
streamedPods := make(map[string]bool)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
processPods := func(wf *v1alpha1.Workflow) {
|
||||
err := util.DecompressWorkflow(wf)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
return
|
||||
}
|
||||
for id := range wf.Status.Nodes {
|
||||
node := wf.Status.Nodes[id]
|
||||
if node.Type == v1alpha1.NodeTypePod && node.Phase != v1alpha1.NodeError && !streamedPods[node.ID] {
|
||||
streamedPods[node.ID] = true
|
||||
go func() {
|
||||
var sinceTimePtr *metav1.Time
|
||||
podTime := timeByPod[node.ID]
|
||||
if podTime != nil {
|
||||
sinceTime := metav1.NewTime(podTime.Add(time.Second))
|
||||
sinceTimePtr = &sinceTime
|
||||
}
|
||||
err := p.getPodLogs(ctx, getDisplayName(node), node.ID, wf.Namespace, true, nil, nil, sinceTimePtr, func(entry logEntry) {
|
||||
logs <- entry
|
||||
})
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer close(logs)
|
||||
fieldSelector := fields.ParseSelectorOrDie(fmt.Sprintf("metadata.name=%s", workflowName))
|
||||
listOpts := metav1.ListOptions{FieldSelector: fieldSelector.String()}
|
||||
lw := &cache.ListWatch{
|
||||
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
return wfClient.List(listOpts)
|
||||
},
|
||||
WatchFunc: func(options metav1.ListOptions) (pkgwatch.Interface, error) {
|
||||
return wfClient.Watch(listOpts)
|
||||
},
|
||||
}
|
||||
_, err := watch.UntilWithSync(ctx, lw, &v1alpha1.Workflow{}, nil, func(event pkgwatch.Event) (b bool, e error) {
|
||||
if wf, ok := event.Object.(*v1alpha1.Workflow); ok {
|
||||
if !wf.Status.Completed() {
|
||||
processPods(wf)
|
||||
}
|
||||
return wf.Status.Completed(), nil
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
for entry := range logs {
|
||||
p.printLogEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func getDisplayName(node v1alpha1.NodeStatus) string {
|
||||
res := node.DisplayName
|
||||
if res == "" {
|
||||
res = node.Name
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (p *logPrinter) printLogEntry(entry logEntry) {
|
||||
line := entry.line
|
||||
if p.timestamps {
|
||||
line = entry.time.Format(time.RFC3339) + " " + line
|
||||
}
|
||||
if entry.displayName != "" {
|
||||
colors := []int{FgRed, FgGreen, FgYellow, FgBlue, FgMagenta, FgCyan, FgWhite, FgDefault}
|
||||
h := fnv.New32a()
|
||||
_, err := h.Write([]byte(entry.displayName))
|
||||
errors.CheckError(err)
|
||||
colorIndex := int(math.Mod(float64(h.Sum32()), float64(len(colors))))
|
||||
line = ansiFormat(entry.displayName, colors[colorIndex]) + ": " + line
|
||||
}
|
||||
fmt.Println(line)
|
||||
}
|
||||
|
||||
func (p *logPrinter) hasContainerStarted(podName string, podNamespace string, container string) (bool, error) {
|
||||
pod, err := p.kubeClient.CoreV1().Pods(podNamespace).Get(podName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
var containerStatus *v1.ContainerStatus
|
||||
for _, status := range pod.Status.ContainerStatuses {
|
||||
if status.Name == container {
|
||||
containerStatus = &status
|
||||
break
|
||||
}
|
||||
}
|
||||
if containerStatus == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if containerStatus.State.Waiting != nil {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (p *logPrinter) getPodLogs(
|
||||
ctx context.Context,
|
||||
displayName string,
|
||||
podName string,
|
||||
podNamespace string,
|
||||
follow bool,
|
||||
tail *int64,
|
||||
sinceSeconds *int64,
|
||||
sinceTime *metav1.Time,
|
||||
callback func(entry logEntry)) error {
|
||||
|
||||
for ctx.Err() == nil {
|
||||
hasStarted, err := p.hasContainerStarted(podName, podNamespace, p.container)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !hasStarted {
|
||||
if follow {
|
||||
time.Sleep(1 * time.Second)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
stream, err := p.kubeClient.CoreV1().Pods(podNamespace).GetLogs(podName, &v1.PodLogOptions{
|
||||
Container: p.container,
|
||||
Follow: follow,
|
||||
Timestamps: true,
|
||||
SinceSeconds: sinceSeconds,
|
||||
SinceTime: sinceTime,
|
||||
TailLines: tail,
|
||||
}).Stream()
|
||||
if err == nil {
|
||||
scanner := bufio.NewScanner(stream)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
parts := strings.Split(line, " ")
|
||||
logTime, err := time.Parse(time.RFC3339, parts[0])
|
||||
if err == nil {
|
||||
lines := strings.Join(parts[1:], " ")
|
||||
for _, line := range strings.Split(lines, "\r") {
|
||||
if line != "" {
|
||||
callback(logEntry{
|
||||
pod: podName,
|
||||
displayName: displayName,
|
||||
time: logTime,
|
||||
line: line,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func mergeSorted(logs [][]logEntry) []logEntry {
|
||||
if len(logs) == 0 {
|
||||
return make([]logEntry, 0)
|
||||
}
|
||||
for len(logs) > 1 {
|
||||
left := logs[0]
|
||||
right := logs[1]
|
||||
size, i, j := len(left)+len(right), 0, 0
|
||||
merged := make([]logEntry, size)
|
||||
|
||||
for k := 0; k < size; k++ {
|
||||
if i > len(left)-1 && j <= len(right)-1 {
|
||||
merged[k] = right[j]
|
||||
j++
|
||||
} else if j > len(right)-1 && i <= len(left)-1 {
|
||||
merged[k] = left[i]
|
||||
i++
|
||||
} else if left[i].time.Before(right[j].time) {
|
||||
merged[k] = left[i]
|
||||
i++
|
||||
} else {
|
||||
merged[k] = right[j]
|
||||
j++
|
||||
}
|
||||
}
|
||||
logs = append(logs[2:], merged)
|
||||
}
|
||||
return logs[0]
|
||||
}
|
46
cmd/argo/resubmit.go
Normal file
46
cmd/argo/resubmit.go
Normal file
@ -0,0 +1,46 @@
|
||||
package argo
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/argoproj/argo/workflow/util"
|
||||
"github.com/argoproj/pkg/errors"
|
||||
|
||||
env "github.com/ian-howell/airshipctl/pkg/environment"
|
||||
)
|
||||
|
||||
func NewResubmitCommand() *cobra.Command {
|
||||
var (
|
||||
memoized bool
|
||||
cliSubmitOpts cliSubmitOpts
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "resubmit WORKFLOW",
|
||||
Short: "resubmit a workflow",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
cmd.HelpFunc()(cmd, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
wfClient := InitWorkflowClient()
|
||||
wf, err := wfClient.Get(args[0], metav1.GetOptions{})
|
||||
errors.CheckError(err)
|
||||
newWF, err := util.FormulateResubmitWorkflow(wf, memoized)
|
||||
errors.CheckError(err)
|
||||
created, err := util.SubmitWorkflow(wfClient, newWF, nil)
|
||||
errors.CheckError(err)
|
||||
printWorkflow(created, cliSubmitOpts.output, env.Default)
|
||||
waitOrWatch([]string{created.Name}, cliSubmitOpts)
|
||||
},
|
||||
}
|
||||
|
||||
command.Flags().StringVarP(&cliSubmitOpts.output, "output", "o", "", "Output format. One of: name|json|yaml|wide")
|
||||
command.Flags().BoolVarP(&cliSubmitOpts.wait, "wait", "w", false, "wait for the workflow to complete")
|
||||
command.Flags().BoolVar(&cliSubmitOpts.watch, "watch", false, "watch the workflow until it completes")
|
||||
command.Flags().BoolVar(&memoized, "memoized", false, "re-use successful steps & outputs from the previous run (experimental)")
|
||||
return command
|
||||
}
|
33
cmd/argo/resume.go
Normal file
33
cmd/argo/resume.go
Normal file
@ -0,0 +1,33 @@
|
||||
package argo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/argoproj/argo/workflow/util"
|
||||
)
|
||||
|
||||
func NewResumeCommand() *cobra.Command {
|
||||
var command = &cobra.Command{
|
||||
Use: "resume WORKFLOW1 WORKFLOW2...",
|
||||
Short: "resume a workflow",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
cmd.HelpFunc()(cmd, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
InitWorkflowClient()
|
||||
for _, wfName := range args {
|
||||
err := util.ResumeWorkflow(wfClient, wfName)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to resume %s: %+v", wfName, err)
|
||||
}
|
||||
fmt.Printf("workflow %s resumed\n", wfName)
|
||||
}
|
||||
},
|
||||
}
|
||||
return command
|
||||
}
|
45
cmd/argo/retry.go
Normal file
45
cmd/argo/retry.go
Normal file
@ -0,0 +1,45 @@
|
||||
package argo
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/argoproj/argo/workflow/util"
|
||||
|
||||
env "github.com/ian-howell/airshipctl/pkg/environment"
|
||||
)
|
||||
|
||||
func NewRetryCommand() *cobra.Command {
|
||||
var (
|
||||
cliSubmitOpts cliSubmitOpts
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "retry WORKFLOW",
|
||||
Short: "retry a workflow",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
cmd.HelpFunc()(cmd, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
kubeClient := initKubeClient()
|
||||
wfClient := InitWorkflowClient()
|
||||
wf, err := wfClient.Get(args[0], metav1.GetOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
wf, err = util.RetryWorkflow(kubeClient, wfClient, wf)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
printWorkflow(wf, cliSubmitOpts.output, env.Default)
|
||||
waitOrWatch([]string{wf.Name}, cliSubmitOpts)
|
||||
},
|
||||
}
|
||||
command.Flags().StringVarP(&cliSubmitOpts.output, "output", "o", "", "Output format. One of: name|json|yaml|wide")
|
||||
command.Flags().BoolVarP(&cliSubmitOpts.wait, "wait", "w", false, "wait for the workflow to complete")
|
||||
command.Flags().BoolVar(&cliSubmitOpts.watch, "watch", false, "watch the workflow until it completes")
|
||||
return command
|
||||
}
|
56
cmd/argo/root.go
Normal file
56
cmd/argo/root.go
Normal file
@ -0,0 +1,56 @@
|
||||
package argo
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
"github.com/argoproj/argo/util/cmd"
|
||||
)
|
||||
|
||||
const (
|
||||
// CLIName is the name of the CLI
|
||||
CLIName = "argo"
|
||||
)
|
||||
|
||||
// NewArgoCommand returns a new instance of an argo command
|
||||
func NewArgoCommand() *cobra.Command {
|
||||
var pluginRootCmd = &cobra.Command{
|
||||
Use: CLIName,
|
||||
Short: "argo is the command line interface to Argo",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd.HelpFunc()(cmd, args)
|
||||
},
|
||||
}
|
||||
|
||||
pluginRootCmd.AddCommand(NewDeleteCommand())
|
||||
pluginRootCmd.AddCommand(NewGetCommand())
|
||||
pluginRootCmd.AddCommand(NewLintCommand())
|
||||
pluginRootCmd.AddCommand(NewListCommand())
|
||||
pluginRootCmd.AddCommand(NewLogsCommand())
|
||||
pluginRootCmd.AddCommand(NewResubmitCommand())
|
||||
pluginRootCmd.AddCommand(NewResumeCommand())
|
||||
pluginRootCmd.AddCommand(NewRetryCommand())
|
||||
pluginRootCmd.AddCommand(NewSubmitCommand())
|
||||
pluginRootCmd.AddCommand(NewSuspendCommand())
|
||||
pluginRootCmd.AddCommand(NewWaitCommand())
|
||||
pluginRootCmd.AddCommand(NewWatchCommand())
|
||||
pluginRootCmd.AddCommand(NewTerminateCommand())
|
||||
pluginRootCmd.AddCommand(cmd.NewVersionCmd(CLIName))
|
||||
|
||||
addKubectlFlagsToCmd(pluginRootCmd)
|
||||
|
||||
return pluginRootCmd
|
||||
}
|
||||
|
||||
func addKubectlFlagsToCmd(cmd *cobra.Command) {
|
||||
// The "usual" clientcmd/kubectl flags
|
||||
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||
loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig
|
||||
overrides := clientcmd.ConfigOverrides{}
|
||||
kflags := clientcmd.RecommendedConfigOverrideFlags("")
|
||||
// cmd.PersistentFlags().StringVar(&loadingRules.ExplicitPath, "kubeconfig", "", "Path to a kube config. Only required if out-of-cluster")
|
||||
clientcmd.BindOverrideFlags(&overrides, cmd.PersistentFlags(), kflags)
|
||||
clientConfig = clientcmd.NewInteractiveDeferredLoadingClientConfig(loadingRules, &overrides, os.Stdin)
|
||||
}
|
158
cmd/argo/submit.go
Normal file
158
cmd/argo/submit.go
Normal file
@ -0,0 +1,158 @@
|
||||
package argo
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
wfv1 "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1"
|
||||
cmdutil "github.com/argoproj/argo/util/cmd"
|
||||
"github.com/argoproj/argo/workflow/common"
|
||||
"github.com/argoproj/argo/workflow/util"
|
||||
"github.com/argoproj/pkg/json"
|
||||
|
||||
env "github.com/ian-howell/airshipctl/pkg/environment"
|
||||
)
|
||||
|
||||
// cliSubmitOpts holds submition options specific to CLI submission (e.g. controlling output)
|
||||
type cliSubmitOpts struct {
|
||||
output string // --output
|
||||
wait bool // --wait
|
||||
watch bool // --watch
|
||||
strict bool // --strict
|
||||
priority *int32 // --priority
|
||||
}
|
||||
|
||||
func NewSubmitCommand() *cobra.Command {
|
||||
var (
|
||||
submitOpts util.SubmitOpts
|
||||
cliSubmitOpts cliSubmitOpts
|
||||
priority int32
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "submit FILE1 FILE2...",
|
||||
Short: "submit a workflow",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
cmd.HelpFunc()(cmd, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
if cmd.Flag("priority").Changed {
|
||||
cliSubmitOpts.priority = &priority
|
||||
}
|
||||
|
||||
SubmitWorkflows(args, &submitOpts, &cliSubmitOpts)
|
||||
},
|
||||
}
|
||||
command.Flags().StringVar(&submitOpts.Name, "name", "", "override metadata.name")
|
||||
command.Flags().StringVar(&submitOpts.GenerateName, "generate-name", "", "override metadata.generateName")
|
||||
command.Flags().StringVar(&submitOpts.Entrypoint, "entrypoint", "", "override entrypoint")
|
||||
command.Flags().StringArrayVarP(&submitOpts.Parameters, "parameter", "p", []string{}, "pass an input parameter")
|
||||
command.Flags().StringVarP(&submitOpts.ParameterFile, "parameter-file", "f", "", "pass a file containing all input parameters")
|
||||
command.Flags().StringVar(&submitOpts.ServiceAccount, "serviceaccount", "", "run all pods in the workflow using specified serviceaccount")
|
||||
command.Flags().StringVar(&submitOpts.InstanceID, "instanceid", "", "submit with a specific controller's instance id label")
|
||||
command.Flags().StringVarP(&cliSubmitOpts.output, "output", "o", "", "Output format. One of: name|json|yaml|wide")
|
||||
command.Flags().BoolVarP(&cliSubmitOpts.wait, "wait", "w", false, "wait for the workflow to complete")
|
||||
command.Flags().BoolVar(&cliSubmitOpts.watch, "watch", false, "watch the workflow until it completes")
|
||||
command.Flags().BoolVar(&cliSubmitOpts.strict, "strict", true, "perform strict workflow validation")
|
||||
command.Flags().Int32Var(&priority, "priority", 0, "workflow priority")
|
||||
return command
|
||||
}
|
||||
|
||||
func SubmitWorkflows(filePaths []string, submitOpts *util.SubmitOpts, cliOpts *cliSubmitOpts) {
|
||||
if submitOpts == nil {
|
||||
submitOpts = &util.SubmitOpts{}
|
||||
}
|
||||
if cliOpts == nil {
|
||||
cliOpts = &cliSubmitOpts{}
|
||||
}
|
||||
defaultWFClient := InitWorkflowClient()
|
||||
var workflows []wfv1.Workflow
|
||||
if len(filePaths) == 1 && filePaths[0] == "-" {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
body, err := ioutil.ReadAll(reader)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
workflows = unmarshalWorkflows(body, cliOpts.strict)
|
||||
} else {
|
||||
for _, filePath := range filePaths {
|
||||
var body []byte
|
||||
var err error
|
||||
if cmdutil.IsURL(filePath) {
|
||||
response, err := http.Get(filePath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
body, err = ioutil.ReadAll(response.Body)
|
||||
_ = response.Body.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
body, err = ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
wfs := unmarshalWorkflows(body, cliOpts.strict)
|
||||
workflows = append(workflows, wfs...)
|
||||
}
|
||||
}
|
||||
|
||||
if cliOpts.watch {
|
||||
if len(workflows) > 1 {
|
||||
log.Fatalf("Cannot watch more than one workflow")
|
||||
}
|
||||
if cliOpts.wait {
|
||||
log.Fatalf("--wait cannot be combined with --watch")
|
||||
}
|
||||
}
|
||||
|
||||
var workflowNames []string
|
||||
for _, wf := range workflows {
|
||||
wf.Spec.Priority = cliOpts.priority
|
||||
wfClient := defaultWFClient
|
||||
if wf.Namespace != "" {
|
||||
wfClient = InitWorkflowClient(wf.Namespace)
|
||||
}
|
||||
created, err := util.SubmitWorkflow(wfClient, &wf, submitOpts)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to submit workflow: %v", err)
|
||||
}
|
||||
printWorkflow(created, cliOpts.output, env.Default)
|
||||
workflowNames = append(workflowNames, created.Name)
|
||||
}
|
||||
waitOrWatch(workflowNames, *cliOpts)
|
||||
}
|
||||
|
||||
// unmarshalWorkflows unmarshals the input bytes as either json or yaml
|
||||
func unmarshalWorkflows(wfBytes []byte, strict bool) []wfv1.Workflow {
|
||||
var wf wfv1.Workflow
|
||||
var jsonOpts []json.JSONOpt
|
||||
if strict {
|
||||
jsonOpts = append(jsonOpts, json.DisallowUnknownFields)
|
||||
}
|
||||
err := json.Unmarshal(wfBytes, &wf, jsonOpts...)
|
||||
if err == nil {
|
||||
return []wfv1.Workflow{wf}
|
||||
}
|
||||
yamlWfs, err := common.SplitYAMLFile(wfBytes, strict)
|
||||
if err == nil {
|
||||
return yamlWfs
|
||||
}
|
||||
log.Fatalf("Failed to parse workflow: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
func waitOrWatch(workflowNames []string, cliSubmitOpts cliSubmitOpts) {
|
||||
if cliSubmitOpts.wait {
|
||||
WaitWorkflows(workflowNames, false, !(cliSubmitOpts.output == env.Default || cliSubmitOpts.output == env.Wide))
|
||||
} else if cliSubmitOpts.watch {
|
||||
watchWorkflow(workflowNames[0])
|
||||
}
|
||||
}
|
33
cmd/argo/suspend.go
Normal file
33
cmd/argo/suspend.go
Normal file
@ -0,0 +1,33 @@
|
||||
package argo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/argoproj/argo/workflow/util"
|
||||
)
|
||||
|
||||
func NewSuspendCommand() *cobra.Command {
|
||||
var command = &cobra.Command{
|
||||
Use: "suspend WORKFLOW1 WORKFLOW2...",
|
||||
Short: "suspend a workflow",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
cmd.HelpFunc()(cmd, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
InitWorkflowClient()
|
||||
for _, wfName := range args {
|
||||
err := util.SuspendWorkflow(wfClient, wfName)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to suspend %s: %v", wfName, err)
|
||||
}
|
||||
fmt.Printf("workflow %s suspended\n", wfName)
|
||||
}
|
||||
},
|
||||
}
|
||||
return command
|
||||
}
|
31
cmd/argo/terminate.go
Normal file
31
cmd/argo/terminate.go
Normal file
@ -0,0 +1,31 @@
|
||||
package argo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/argoproj/argo/workflow/util"
|
||||
"github.com/argoproj/pkg/errors"
|
||||
)
|
||||
|
||||
func NewTerminateCommand() *cobra.Command {
|
||||
var command = &cobra.Command{
|
||||
Use: "terminate WORKFLOW WORKFLOW2...",
|
||||
Short: "terminate a workflow",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
cmd.HelpFunc()(cmd, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
InitWorkflowClient()
|
||||
for _, name := range args {
|
||||
err := util.TerminateWorkflow(wfClient, name)
|
||||
errors.CheckError(err)
|
||||
fmt.Printf("Workflow '%s' terminated\n", name)
|
||||
}
|
||||
},
|
||||
}
|
||||
return command
|
||||
}
|
83
cmd/argo/wait.go
Normal file
83
cmd/argo/wait.go
Normal file
@ -0,0 +1,83 @@
|
||||
package argo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
apierr "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
|
||||
wfv1 "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1"
|
||||
"github.com/argoproj/pkg/errors"
|
||||
)
|
||||
|
||||
func NewWaitCommand() *cobra.Command {
|
||||
var (
|
||||
ignoreNotFound bool
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "wait WORKFLOW1 WORKFLOW2..,",
|
||||
Short: "waits for a workflow to complete",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
cmd.HelpFunc()(cmd, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
InitWorkflowClient()
|
||||
WaitWorkflows(args, ignoreNotFound, false)
|
||||
},
|
||||
}
|
||||
command.Flags().BoolVar(&ignoreNotFound, "ignore-not-found", false, "Ignore the wait if the workflow is not found")
|
||||
return command
|
||||
}
|
||||
|
||||
// WaitWorkflows waits for the given workflowNames.
|
||||
func WaitWorkflows(workflowNames []string, ignoreNotFound, quiet bool) {
|
||||
var wg sync.WaitGroup
|
||||
for _, workflowName := range workflowNames {
|
||||
wg.Add(1)
|
||||
go func(name string) {
|
||||
waitOnOne(name, ignoreNotFound, quiet)
|
||||
wg.Done()
|
||||
}(workflowName)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func waitOnOne(workflowName string, ignoreNotFound, quiet bool) {
|
||||
fieldSelector := fields.ParseSelectorOrDie(fmt.Sprintf("metadata.name=%s", workflowName))
|
||||
opts := metav1.ListOptions{
|
||||
FieldSelector: fieldSelector.String(),
|
||||
}
|
||||
|
||||
_, err := wfClient.Get(workflowName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if apierr.IsNotFound(err) && ignoreNotFound {
|
||||
return
|
||||
}
|
||||
errors.CheckError(err)
|
||||
}
|
||||
|
||||
watchIf, err := wfClient.Watch(opts)
|
||||
errors.CheckError(err)
|
||||
defer watchIf.Stop()
|
||||
for {
|
||||
next := <-watchIf.ResultChan()
|
||||
wf, _ := next.Object.(*wfv1.Workflow)
|
||||
if wf == nil {
|
||||
watchIf.Stop()
|
||||
watchIf, err = wfClient.Watch(opts)
|
||||
errors.CheckError(err)
|
||||
continue
|
||||
}
|
||||
if !wf.Status.FinishedAt.IsZero() {
|
||||
if !quiet {
|
||||
fmt.Printf("%s completed at %v\n", workflowName, wf.Status.FinishedAt)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
67
cmd/argo/watch.go
Normal file
67
cmd/argo/watch.go
Normal file
@ -0,0 +1,67 @@
|
||||
package argo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
|
||||
wfv1 "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1"
|
||||
"github.com/argoproj/argo/workflow/util"
|
||||
"github.com/argoproj/pkg/errors"
|
||||
)
|
||||
|
||||
func NewWatchCommand() *cobra.Command {
|
||||
var command = &cobra.Command{
|
||||
Use: "watch WORKFLOW",
|
||||
Short: "watch a workflow until it completes",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 1 {
|
||||
cmd.HelpFunc()(cmd, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
InitWorkflowClient()
|
||||
watchWorkflow(args[0])
|
||||
},
|
||||
}
|
||||
return command
|
||||
}
|
||||
|
||||
func watchWorkflow(name string) {
|
||||
fieldSelector := fields.ParseSelectorOrDie(fmt.Sprintf("metadata.name=%s", name))
|
||||
opts := metav1.ListOptions{
|
||||
FieldSelector: fieldSelector.String(),
|
||||
}
|
||||
wf, err := wfClient.Get(name, metav1.GetOptions{})
|
||||
errors.CheckError(err)
|
||||
|
||||
watchIf, err := wfClient.Watch(opts)
|
||||
errors.CheckError(err)
|
||||
ticker := time.NewTicker(time.Second)
|
||||
|
||||
for {
|
||||
select {
|
||||
case next := <-watchIf.ResultChan():
|
||||
wf, _ = next.Object.(*wfv1.Workflow)
|
||||
case <-ticker.C:
|
||||
}
|
||||
if wf == nil {
|
||||
watchIf.Stop()
|
||||
watchIf, err = wfClient.Watch(opts)
|
||||
errors.CheckError(err)
|
||||
continue
|
||||
}
|
||||
err := util.DecompressWorkflow(wf)
|
||||
errors.CheckError(err)
|
||||
print("\033[H\033[2J")
|
||||
print("\033[0;0H")
|
||||
printWorkflowHelper(wf, getFlags{})
|
||||
if !wf.Status.FinishedAt.IsZero() {
|
||||
break
|
||||
}
|
||||
}
|
||||
watchIf.Stop()
|
||||
}
|
@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/ian-howell/airshipctl/cmd/argo"
|
||||
"github.com/ian-howell/airshipctl/cmd/bootstrap"
|
||||
"github.com/ian-howell/airshipctl/pkg/environment"
|
||||
"github.com/ian-howell/airshipctl/pkg/log"
|
||||
@ -41,5 +42,6 @@ func NewRootCmd(out io.Writer) (*cobra.Command, *environment.AirshipCTLSettings,
|
||||
// default commands to airshipctl
|
||||
func AddDefaultAirshipCTLCommands(cmd *cobra.Command, settings *environment.AirshipCTLSettings) *cobra.Command {
|
||||
cmd.AddCommand(bootstrap.NewBootstrapCommand(settings))
|
||||
cmd.AddCommand(argo.NewArgoCommand())
|
||||
return cmd
|
||||
}
|
||||
|
30
go.mod
30
go.mod
@ -4,8 +4,16 @@ go 1.12
|
||||
|
||||
require (
|
||||
github.com/OpenPeeDeeP/depguard v0.0.0-20181229194401-1f388ab2d810 // indirect
|
||||
github.com/argoproj/argo v2.3.0+incompatible
|
||||
github.com/argoproj/pkg v0.0.0-20190409001913-7e3ef65c8d44
|
||||
github.com/colinmarc/hdfs v0.0.0-20180802165501-48eb8d6c34a9
|
||||
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c // indirect
|
||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||
github.com/emicklei/go-restful v2.9.6+incompatible // indirect
|
||||
github.com/fatih/color v1.7.0 // indirect
|
||||
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32
|
||||
github.com/go-critic/go-critic v0.3.4 // indirect
|
||||
github.com/go-openapi/spec v0.19.0 // indirect
|
||||
github.com/golang/mock v1.3.1 // indirect
|
||||
github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6 // indirect
|
||||
github.com/golangci/go-tools v0.0.0-20190124090046-35a9f45a5db0 // indirect
|
||||
@ -16,31 +24,45 @@ require (
|
||||
github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219 // indirect
|
||||
github.com/golangci/revgrep v0.0.0-20180812185044-276a5c0a1039 // indirect
|
||||
github.com/google/gofuzz v1.0.0 // indirect
|
||||
github.com/googleapis/gnostic v0.2.0 // indirect
|
||||
github.com/gostaticanalysis/analysisutil v0.0.0-20190329151158-56bca42c7635 // indirect
|
||||
github.com/hashicorp/go-uuid v1.0.1 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.1 // indirect
|
||||
github.com/imdario/mergo v0.3.7 // indirect
|
||||
github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03 // indirect
|
||||
github.com/json-iterator/go v1.1.6 // indirect
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
||||
github.com/magiconair/properties v1.8.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.2 // indirect
|
||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d // indirect
|
||||
github.com/onsi/ginkgo v1.8.0 // indirect
|
||||
github.com/onsi/gomega v1.5.0 // indirect
|
||||
github.com/pelletier/go-toml v1.4.0 // indirect
|
||||
github.com/pkg/errors v0.8.1 // indirect
|
||||
github.com/shurcooL/go v0.0.0-20190330031554-6713ea532688 // indirect
|
||||
github.com/sirupsen/logrus v1.4.2 // indirect
|
||||
github.com/spf13/afero v1.2.2 // indirect
|
||||
github.com/sirupsen/logrus v1.4.2
|
||||
github.com/spf13/cobra v0.0.5
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/viper v1.4.0 // indirect
|
||||
github.com/stretchr/testify v1.3.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.0.1 // indirect
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522 // indirect
|
||||
golang.org/x/net v0.0.0-20190607181551-461777fb6f67 // indirect
|
||||
golang.org/x/sys v0.0.0-20190610081024-1e42afee0f76 // indirect
|
||||
golang.org/x/text v0.3.2 // indirect
|
||||
golang.org/x/tools v0.0.0-20190608022120-eacb66d2a7c3 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
k8s.io/apimachinery v0.0.0-20190515023456-b74e4c97951f
|
||||
gopkg.in/jcmturner/aescts.v1 v1.0.1 // indirect
|
||||
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 // indirect
|
||||
gopkg.in/jcmturner/gokrb5.v5 v5.3.0 // indirect
|
||||
gopkg.in/jcmturner/rpc.v0 v0.0.2 // indirect
|
||||
k8s.io/api v0.0.0-20190516230258-a675ac48af67
|
||||
k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d
|
||||
k8s.io/client-go v11.0.1-0.20190516230509-ae8359b20417+incompatible
|
||||
k8s.io/code-generator v0.0.0-20190511023357-639c964206c2
|
||||
k8s.io/kube-openapi v0.0.0-20190603182131-db7b694dc208 // indirect
|
||||
k8s.io/utils v0.0.0-20190607212802-c55fbcfc754a // indirect
|
||||
mvdan.cc/unparam v0.0.0-20190310220240-1b9ccfa71afe // indirect
|
||||
sigs.k8s.io/yaml v1.1.0 // indirect
|
||||
sourcegraph.com/sqs/pbtypes v1.0.0 // indirect
|
||||
)
|
||||
|
104
go.sum
104
go.sum
@ -2,19 +2,34 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/OpenPeeDeeP/depguard v0.0.0-20180806142446-a69c782687b2 h1:HTOmFEEYrWi4MW5ZKUx6xfeyM10Sx3kQF65xiQJMPYA=
|
||||
github.com/OpenPeeDeeP/depguard v0.0.0-20180806142446-a69c782687b2/go.mod h1:7/4sitnI9YlQgTLLk734QlzXT8DuHVnAyztLplQjk+o=
|
||||
github.com/OpenPeeDeeP/depguard v0.0.0-20181229194401-1f388ab2d810 h1:pFks+oaqVWDFq0KsLQZFB2pQGB5Us0HfMSBENtieXOs=
|
||||
github.com/OpenPeeDeeP/depguard v0.0.0-20181229194401-1f388ab2d810/go.mod h1:7/4sitnI9YlQgTLLk734QlzXT8DuHVnAyztLplQjk+o=
|
||||
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4=
|
||||
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/argoproj/argo v2.3.0+incompatible h1:L1OYZ86Q7NK19ahdl/eJOq78Mlf52wUKGmp7VDNQVz8=
|
||||
github.com/argoproj/argo v2.3.0+incompatible/go.mod h1:KJ0MB+tuhtAklR4jkPM10mIZXfRA0peTYJ1sLUnFLVU=
|
||||
github.com/argoproj/pkg v0.0.0-20190409001913-7e3ef65c8d44 h1:fqYoz7qu4K8/mBdm/N1p7qKtdPhlwOSHlTQoAu4rATs=
|
||||
github.com/argoproj/pkg v0.0.0-20190409001913-7e3ef65c8d44/go.mod h1:2EZ44RG/CcgtPTwrRR0apOc7oU6UIw8GjCUJWZ8X3bM=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/colinmarc/hdfs v0.0.0-20180802165501-48eb8d6c34a9 h1:hJqIlFDcaaBLEBkCuqyEtzSsloo/h+lm08Qrq1OM/e8=
|
||||
github.com/colinmarc/hdfs v0.0.0-20180802165501-48eb8d6c34a9/go.mod h1:0DumPviB681UcSuJErAbDIOx6SIaJWj463TymfZG02I=
|
||||
github.com/colinmarc/hdfs v1.1.3 h1:662salalXLFmp+ctD+x0aG+xOg62lnVnOJHksXYpFBw=
|
||||
github.com/colinmarc/hdfs v1.1.3/go.mod h1:0DumPviB681UcSuJErAbDIOx6SIaJWj463TymfZG02I=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
@ -22,13 +37,22 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 h1:cenwrSVm+Z7QLSV/BsnenAOcDXdX4cMv4wP0B/5QbPg=
|
||||
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c h1:ZfSZ3P3BedhKGUhzj7BQlPSU4OvT6tfOKe3DVHzOA7s=
|
||||
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/emicklei/go-restful v2.9.6+incompatible h1:tfrHha8zJ01ywiOEC1miGY8st1/igzWB8OmvPgoYX7w=
|
||||
github.com/emicklei/go-restful v2.9.6+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550 h1:mV9jbLoSW/8m4VK16ZkHTozJa8sesK5u5kTMFysTYac=
|
||||
github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/fatih/color v1.6.0 h1:66qjqZk8kalYAvDRtM1AdAJQI0tj4Wrue3Eq3B3pmFU=
|
||||
@ -37,7 +61,11 @@ github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew=
|
||||
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
||||
github.com/go-critic/go-critic v0.0.0-20181204210945-1df300866540/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA=
|
||||
github.com/go-critic/go-critic v0.3.4 h1:FYaiaLjX0Nqei80KPhm4CyFQUBbmJwSrHxQ73taaGBc=
|
||||
github.com/go-critic/go-critic v0.3.4/go.mod h1:AHR42Lk/E/aOznsrYdMYeIQS5RH10HZHSqP+rD6AJrc=
|
||||
@ -48,6 +76,18 @@ github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTD
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
|
||||
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
|
||||
github.com/go-openapi/jsonpointer v0.17.0 h1:nH6xp8XdXHx8dqveo0ZuJBluCO2qGrPbDNZ0dwoRHP0=
|
||||
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
|
||||
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
|
||||
github.com/go-openapi/jsonreference v0.17.0 h1:yJW3HCkTHg7NOA+gZ83IPHzUSnUzGXhGmsdiCcMexbA=
|
||||
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
|
||||
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
|
||||
github.com/go-openapi/spec v0.19.0 h1:A4SZ6IWh3lnjH0rG0Z5lkxazMGBECtrZcbyYQi+64k4=
|
||||
github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
|
||||
github.com/go-openapi/swag v0.17.0 h1:iqrgMg7Q7SvtbWLlltPrkMs0UBJI6oTSs79JFRUi880=
|
||||
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-toolsmith/astcast v0.0.0-20181028201508-b7a89ed70af1 h1:h+1eMw+tZAlgTVclcVN0/rdPaBI/RUzG0peblT6df+Q=
|
||||
github.com/go-toolsmith/astcast v0.0.0-20181028201508-b7a89ed70af1/go.mod h1:TEo3Ghaj7PsZawQHxT/oBvo4HK/sl1RcuUHDKTTju+o=
|
||||
@ -96,6 +136,7 @@ github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@ -151,11 +192,17 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
||||
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
||||
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gnostic v0.0.0-20170426233943-68f4ded48ba9/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d h1:7XGaL1e6bYS1yIonGp9761ExpPPV1ui0SAC59Yube9k=
|
||||
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g=
|
||||
github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
|
||||
github.com/gostaticanalysis/analysisutil v0.0.0-20190329151158-56bca42c7635 h1:I/ckdXlVHde3unRCAcN/Tcpu7LFwgvyHqnFTeklC9oA=
|
||||
@ -163,16 +210,26 @@ github.com/gostaticanalysis/analysisutil v0.0.0-20190329151158-56bca42c7635/go.m
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce h1:xdsDDbiBDQTKASoGEZ+pEmF1OnWuu8AQ9I8iNbHNeno=
|
||||
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI=
|
||||
github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03 h1:FUwcHNlEqkqLjLBdCp5PRlCFijNjvcYANOZXzCfXwCM=
|
||||
github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
@ -201,6 +258,9 @@ github.com/magiconair/properties v1.7.6/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic=
|
||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
||||
@ -221,9 +281,11 @@ github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQz
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mozilla/tls-observatory v0.0.0-20180409132520-8791a200eb40/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
|
||||
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20160627004424-a22cb81b2ecd/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
|
||||
@ -253,6 +315,7 @@ github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
@ -305,6 +368,7 @@ github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec/go.mod h1:
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
@ -315,6 +379,7 @@ github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
|
||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
@ -324,8 +389,11 @@ github.com/timakin/bodyclose v0.0.0-20190407043127-4a873e97b2bb/go.mod h1:Qimiff
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s=
|
||||
github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
|
||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
github.com/valyala/quicktemplate v1.1.1/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4=
|
||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
@ -337,6 +405,7 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a h1:YX8ljsm6wXlHZO+aRz9Exqr0evNhKRNe5K/gi+zKh4U=
|
||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495 h1:I6A9Ag9FpEKOjcKrRNjQkPHawoXIhKyTGfvvjFAiiAk=
|
||||
@ -347,10 +416,12 @@ golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMx
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20170915142106-8351a756f30f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@ -359,11 +430,13 @@ golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190607181551-461777fb6f67 h1:rJJxsykSlULwd2P2+pg/rtnwN2FrWp4IuCxOSyS0V00=
|
||||
golang.org/x/net v0.0.0-20190607181551-461777fb6f67/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20171026204733-164713f0dfce/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@ -378,6 +451,7 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/p
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190610081024-1e42afee0f76 h1:QSmW7Q3mFdAGjtAd0byXmFJ55inUydyZ4WQmiuItAIQ=
|
||||
golang.org/x/sys v0.0.0-20190610081024-1e42afee0f76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.0.0-20170915090833-1cbadb444a80/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db h1:6/JqlYfC1CCaLnGceQTI+sDGhC9UBSPAsBqI0Gun6kU=
|
||||
@ -389,6 +463,7 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxb
|
||||
golang.org/x/tools v0.0.0-20170915040203-e531a2a1c15f/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181205014116-22934f0fdb62/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@ -427,6 +502,14 @@ gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKW
|
||||
gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw=
|
||||
gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
|
||||
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN5hSM=
|
||||
gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q=
|
||||
gopkg.in/jcmturner/gokrb5.v5 v5.3.0 h1:RS1MYApX27Hx1Xw7NECs7XxGxxrm69/4OmaRuX9kwec=
|
||||
gopkg.in/jcmturner/gokrb5.v5 v5.3.0/go.mod h1:oQz8Wc5GsctOTgCVyKad1Vw4TCWz5G6gfIQr88RPv4k=
|
||||
gopkg.in/jcmturner/rpc.v0 v0.0.2 h1:wBTgrbL1qmLBUPsYVCqdJiI5aJgQhexmK+JkTHPUNJI=
|
||||
gopkg.in/jcmturner/rpc.v0 v0.0.2/go.mod h1:NzMq6cRzR9lipgw7WxRBHNx5N8SifBuaCQsOT1kWY/E=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
@ -436,16 +519,36 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
k8s.io/api v0.0.0-20190516230258-a675ac48af67 h1:BKg03K4me3EdM340RB08XB3MRlJ57KY+0f5PfI6wPZY=
|
||||
k8s.io/api v0.0.0-20190516230258-a675ac48af67/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA=
|
||||
k8s.io/api v0.0.0-20190612125737-db0771252981 h1:DN1D/gMpl+h70Ek3Gb2ykCEI0QqIUtJ2e2z9PnAYz+Q=
|
||||
k8s.io/api v0.0.0-20190612125737-db0771252981/go.mod h1:SR4nMi8IQTDnEi4768MsMCoZ9DyfRls7wy+TbRrFicA=
|
||||
k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d h1:Jmdtdt1ZnoGfWWIIik61Z7nKYgO3J+swQJtPYsP9wHA=
|
||||
k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
|
||||
k8s.io/apimachinery v0.0.0-20190515023456-b74e4c97951f h1:cBrF1gFrJrvimOHZzyEHrvtlfqPV+KM7QZt3M0mepEg=
|
||||
k8s.io/apimachinery v0.0.0-20190515023456-b74e4c97951f/go.mod h1:Ew3b/24/JSgJdn4RsnrLskv3LvMZDlZ1Fl1xopsJftY=
|
||||
k8s.io/apimachinery v0.0.0-20190612125636-6a5db36e93ad h1:x1lITOfDEbnzt8D1cZJsPbdnx/hnv28FxY2GKkxmxgU=
|
||||
k8s.io/apimachinery v0.0.0-20190612125636-6a5db36e93ad/go.mod h1:I4A+glKBHiTgiEjQiCCQfCAIcIMFGt291SmsvcrFzJA=
|
||||
k8s.io/client-go v11.0.0+incompatible h1:LBbX2+lOwY9flffWlJM7f1Ct8V2SRNiMRDFeiwnJo9o=
|
||||
k8s.io/client-go v11.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
|
||||
k8s.io/client-go v11.0.1-0.20190516230509-ae8359b20417+incompatible h1:bK03DJulJi9j05gwnXUufcs2j7h4M85YFvJ0dIlQ9k4=
|
||||
k8s.io/client-go v11.0.1-0.20190516230509-ae8359b20417+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
|
||||
k8s.io/code-generator v0.0.0-20190511023357-639c964206c2 h1:wfF2JZb8Bl68FNMg/BAkIkkE29Z/bXWBYTtoQh/Cbo0=
|
||||
k8s.io/code-generator v0.0.0-20190511023357-639c964206c2/go.mod h1:YMQ7Lt97nW/I6nHACDccgS/sPAyrHQNans96RwPaSb8=
|
||||
k8s.io/gengo v0.0.0-20190116091435-f8a0810f38af h1:SwjZbO0u5ZuaV6TRMWOGB40iaycX8sbdMQHtjNZ19dk=
|
||||
k8s.io/gengo v0.0.0-20190116091435-f8a0810f38af/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/klog v0.3.0 h1:0VPpR+sizsiivjIfIAQH/rl8tan6jvWkS7lU+0di3lE=
|
||||
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/klog v0.3.1 h1:RVgyDHY/kFKtLqh67NvEWIgkMneNoIrdkN0CxDSQc68=
|
||||
k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30 h1:TRb4wNWoBVrH9plmkp2q86FIDppkbrEXdXlxU3a3BMI=
|
||||
k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc=
|
||||
k8s.io/kube-openapi v0.0.0-20190603182131-db7b694dc208 h1:5sW+fEHvlJI3Ngolx30CmubFulwH28DhKjGf70Xmtco=
|
||||
k8s.io/kube-openapi v0.0.0-20190603182131-db7b694dc208/go.mod h1:nfDlWeOsu3pUf4yWGL+ERqohP4YsZcBJXWMK+gkzOA4=
|
||||
k8s.io/utils v0.0.0-20190607212802-c55fbcfc754a h1:2jUDc9gJja832Ftp+QbDV0tVhQHMISFn01els+2ZAcw=
|
||||
k8s.io/utils v0.0.0-20190607212802-c55fbcfc754a/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
||||
modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=
|
||||
modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
|
||||
modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
|
||||
@ -459,6 +562,7 @@ mvdan.cc/unparam v0.0.0-20190124213536-fbb59629db34 h1:B1LAOfRqg2QUyCdzfjf46quTS
|
||||
mvdan.cc/unparam v0.0.0-20190124213536-fbb59629db34/go.mod h1:H6SUd1XjIs+qQCyskXg5OFSrilMRUkD8ePJpHKDPaeY=
|
||||
mvdan.cc/unparam v0.0.0-20190310220240-1b9ccfa71afe h1:Ekmnp+NcP2joadI9pbK4Bva87QKZSeY7le//oiMrc9g=
|
||||
mvdan.cc/unparam v0.0.0-20190310220240-1b9ccfa71afe/go.mod h1:BnhuWBAqxH3+J5bDybdxgw5ZfS+DsVd4iylsKQePN8o=
|
||||
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
|
||||
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4 h1:JPJh2pk3+X4lXAkZIk2RuE/7/FoK9maXw+TNPJhVS/c=
|
||||
|
13
pkg/environment/constants.go
Normal file
13
pkg/environment/constants.go
Normal file
@ -0,0 +1,13 @@
|
||||
package environment
|
||||
|
||||
// OutputFormat denotes the form with which to display tabulated data
|
||||
type OutputFormat string
|
||||
|
||||
// These are valid values for OutputFormat
|
||||
const (
|
||||
Default = ""
|
||||
JSON = "json"
|
||||
YAML = "yaml"
|
||||
NameOnly = "name"
|
||||
Wide = "wide"
|
||||
)
|
Loading…
Reference in New Issue
Block a user