github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/command/alloc_logs.go (about)

     1  package command
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"os/signal"
     9  	"strings"
    10  	"syscall"
    11  	"time"
    12  
    13  	"github.com/hashicorp/nomad/api"
    14  	"github.com/hashicorp/nomad/api/contexts"
    15  	"github.com/posener/complete"
    16  )
    17  
    18  type AllocLogsCommand struct {
    19  	Meta
    20  }
    21  
    22  func (l *AllocLogsCommand) Help() string {
    23  	helpText := `
    24  Usage: nomad alloc logs [options] <allocation> <task>
    25  Alias: nomad logs
    26  
    27    Streams the stdout/stderr of the given allocation and task.
    28  
    29    When ACLs are enabled, this command requires a token with the 'read-logs',
    30    'read-job', and 'list-jobs' capabilities for the allocation's namespace.
    31  
    32  General Options:
    33  
    34    ` + generalOptionsUsage(usageOptsDefault) + `
    35  
    36  Logs Specific Options:
    37  
    38    -stderr
    39      Display stderr logs.
    40  
    41    -verbose
    42      Show full information.
    43  
    44    -task <task-name>
    45      Sets the task to view the logs.
    46  
    47    -job <job-id>
    48      Use a random allocation from the specified job ID.
    49  
    50    -f
    51      Causes the output to not stop when the end of the logs are reached, but
    52      rather to wait for additional output.
    53  
    54    -tail
    55      Show the logs contents with offsets relative to the end of the logs. If no
    56      offset is given, -n is defaulted to 10.
    57  
    58    -n
    59      Sets the tail location in best-efforted number of lines relative to the end
    60      of the logs.
    61  
    62    -c
    63      Sets the tail location in number of bytes relative to the end of the logs.
    64  
    65    Note that the -no-color option applies to Nomad's own output. If the task's
    66    logs include terminal escape sequences for color codes, Nomad will not
    67    remove them.
    68  `
    69  
    70  	return strings.TrimSpace(helpText)
    71  }
    72  
    73  func (l *AllocLogsCommand) Synopsis() string {
    74  	return "Streams the logs of a task."
    75  }
    76  
    77  func (c *AllocLogsCommand) AutocompleteFlags() complete.Flags {
    78  	return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
    79  		complete.Flags{
    80  			"-stderr":  complete.PredictNothing,
    81  			"-verbose": complete.PredictNothing,
    82  			"-task":    complete.PredictAnything,
    83  			"-job":     complete.PredictAnything,
    84  			"-f":       complete.PredictNothing,
    85  			"-tail":    complete.PredictAnything,
    86  			"-n":       complete.PredictAnything,
    87  			"-c":       complete.PredictAnything,
    88  		})
    89  }
    90  
    91  func (l *AllocLogsCommand) AutocompleteArgs() complete.Predictor {
    92  	return complete.PredictFunc(func(a complete.Args) []string {
    93  		client, err := l.Meta.Client()
    94  		if err != nil {
    95  			return nil
    96  		}
    97  
    98  		resp, _, err := client.Search().PrefixSearch(a.Last, contexts.Allocs, nil)
    99  		if err != nil {
   100  			return []string{}
   101  		}
   102  		return resp.Matches[contexts.Allocs]
   103  	})
   104  }
   105  
   106  func (l *AllocLogsCommand) Name() string { return "alloc logs" }
   107  
   108  func (l *AllocLogsCommand) Run(args []string) int {
   109  	var verbose, job, tail, stderr, follow bool
   110  	var numLines, numBytes int64
   111  	var task string
   112  
   113  	flags := l.Meta.FlagSet(l.Name(), FlagSetClient)
   114  	flags.Usage = func() { l.Ui.Output(l.Help()) }
   115  	flags.BoolVar(&verbose, "verbose", false, "")
   116  	flags.BoolVar(&job, "job", false, "")
   117  	flags.BoolVar(&tail, "tail", false, "")
   118  	flags.BoolVar(&follow, "f", false, "")
   119  	flags.BoolVar(&stderr, "stderr", false, "")
   120  	flags.Int64Var(&numLines, "n", -1, "")
   121  	flags.Int64Var(&numBytes, "c", -1, "")
   122  	flags.StringVar(&task, "task", "", "")
   123  
   124  	if err := flags.Parse(args); err != nil {
   125  		return 1
   126  	}
   127  	args = flags.Args()
   128  
   129  	if numArgs := len(args); numArgs < 1 {
   130  		if job {
   131  			l.Ui.Error("A job ID is required")
   132  		} else {
   133  			l.Ui.Error("An allocation ID is required")
   134  		}
   135  
   136  		l.Ui.Error(commandErrorText(l))
   137  		return 1
   138  	} else if numArgs > 2 {
   139  		l.Ui.Error("This command takes one or two arguments")
   140  		l.Ui.Error(commandErrorText(l))
   141  		return 1
   142  	}
   143  
   144  	client, err := l.Meta.Client()
   145  	if err != nil {
   146  		l.Ui.Error(fmt.Sprintf("Error initializing client: %v", err))
   147  		return 1
   148  	}
   149  
   150  	// If -job is specified, use random allocation, otherwise use provided allocation
   151  	allocID := args[0]
   152  	if job {
   153  		allocID, err = getRandomJobAllocID(client, args[0])
   154  		if err != nil {
   155  			l.Ui.Error(fmt.Sprintf("Error fetching allocations: %v", err))
   156  			return 1
   157  		}
   158  	}
   159  
   160  	// Truncate the id unless full length is requested
   161  	length := shortId
   162  	if verbose {
   163  		length = fullId
   164  	}
   165  	// Query the allocation info
   166  	if len(allocID) == 1 {
   167  		l.Ui.Error("Alloc ID must contain at least two characters.")
   168  		return 1
   169  	}
   170  
   171  	allocID = sanitizeUUIDPrefix(allocID)
   172  	allocs, _, err := client.Allocations().PrefixList(allocID)
   173  	if err != nil {
   174  		l.Ui.Error(fmt.Sprintf("Error querying allocation: %v", err))
   175  		return 1
   176  	}
   177  	if len(allocs) == 0 {
   178  		l.Ui.Error(fmt.Sprintf("No allocation(s) with prefix or id %q found", allocID))
   179  		return 1
   180  	}
   181  	if len(allocs) > 1 {
   182  		// Format the allocs
   183  		out := formatAllocListStubs(allocs, verbose, length)
   184  		l.Ui.Error(fmt.Sprintf("Prefix matched multiple allocations\n\n%s", out))
   185  		return 1
   186  	}
   187  	// Prefix lookup matched a single allocation
   188  	q := &api.QueryOptions{Namespace: allocs[0].Namespace}
   189  	alloc, _, err := client.Allocations().Info(allocs[0].ID, q)
   190  	if err != nil {
   191  		l.Ui.Error(fmt.Sprintf("Error querying allocation: %s", err))
   192  		return 1
   193  	}
   194  
   195  	// If -task isn't provided fallback to reading the task name
   196  	// from args.
   197  	if task != "" {
   198  		err = validateTaskExistsInAllocation(task, alloc)
   199  	} else {
   200  		if len(args) >= 2 {
   201  			task = args[1]
   202  			if task == "" {
   203  				l.Ui.Error("Task name required")
   204  				return 1
   205  			}
   206  		} else {
   207  			task, err = lookupAllocTask(alloc)
   208  		}
   209  	}
   210  	if err != nil {
   211  		l.Ui.Error(fmt.Sprintf("Failed to validate task: %s", err))
   212  		return 1
   213  	}
   214  
   215  	logType := "stdout"
   216  	if stderr {
   217  		logType = "stderr"
   218  	}
   219  
   220  	// We have a file, output it.
   221  	var r io.ReadCloser
   222  	var readErr error
   223  	if !tail {
   224  		r, readErr = l.followFile(client, alloc, follow, task, logType, api.OriginStart, 0)
   225  		if readErr != nil {
   226  			readErr = fmt.Errorf("Error reading file: %v", readErr)
   227  		}
   228  	} else {
   229  		// Parse the offset
   230  		var offset int64 = defaultTailLines * bytesToLines
   231  
   232  		if nLines, nBytes := numLines != -1, numBytes != -1; nLines && nBytes {
   233  			l.Ui.Error("Both -n and -c set")
   234  			return 1
   235  		} else if nLines {
   236  			offset = numLines * bytesToLines
   237  		} else if nBytes {
   238  			offset = numBytes
   239  		} else {
   240  			numLines = defaultTailLines
   241  		}
   242  
   243  		r, readErr = l.followFile(client, alloc, follow, task, logType, api.OriginEnd, offset)
   244  
   245  		// If numLines is set, wrap the reader
   246  		if numLines != -1 {
   247  			r = NewLineLimitReader(r, int(numLines), int(numLines*bytesToLines), 1*time.Second)
   248  		}
   249  
   250  		if readErr != nil {
   251  			readErr = fmt.Errorf("Error tailing file: %v", readErr)
   252  		}
   253  	}
   254  
   255  	if readErr != nil {
   256  		l.Ui.Error(readErr.Error())
   257  		return 1
   258  	}
   259  
   260  	defer r.Close()
   261  	_, err = io.Copy(os.Stdout, r)
   262  	if err != nil {
   263  		l.Ui.Error(fmt.Sprintf("error following logs: %s", err))
   264  		return 1
   265  	}
   266  
   267  	return 0
   268  }
   269  
   270  // followFile outputs the contents of the file to stdout relative to the end of
   271  // the file.
   272  func (l *AllocLogsCommand) followFile(client *api.Client, alloc *api.Allocation,
   273  	follow bool, task, logType, origin string, offset int64) (io.ReadCloser, error) {
   274  
   275  	cancel := make(chan struct{})
   276  	frames, errCh := client.AllocFS().Logs(alloc, follow, task, logType, origin, offset, cancel, nil)
   277  	select {
   278  	case err := <-errCh:
   279  		return nil, err
   280  	default:
   281  	}
   282  	signalCh := make(chan os.Signal, 1)
   283  	signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM)
   284  
   285  	// Create a reader
   286  	var r io.ReadCloser
   287  	frameReader := api.NewFrameReader(frames, errCh, cancel)
   288  	frameReader.SetUnblockTime(500 * time.Millisecond)
   289  	r = frameReader
   290  
   291  	go func() {
   292  		<-signalCh
   293  
   294  		// End the streaming
   295  		r.Close()
   296  	}()
   297  
   298  	return r, nil
   299  }
   300  
   301  func lookupAllocTask(alloc *api.Allocation) (string, error) {
   302  	tg := alloc.Job.LookupTaskGroup(alloc.TaskGroup)
   303  	if tg == nil {
   304  		return "", fmt.Errorf("Could not find allocation task group: %s", alloc.TaskGroup)
   305  	}
   306  
   307  	if len(tg.Tasks) == 1 {
   308  		return tg.Tasks[0].Name, nil
   309  	}
   310  
   311  	var errStr strings.Builder
   312  	fmt.Fprintf(&errStr, "Allocation %q is running the following tasks:\n", limit(alloc.ID, shortId))
   313  	for _, t := range tg.Tasks {
   314  		fmt.Fprintf(&errStr, "  * %s\n", t.Name)
   315  	}
   316  	fmt.Fprintf(&errStr, "\nPlease specify the task.")
   317  	return "", errors.New(errStr.String())
   318  }