github.com/moby/docker@v26.1.3+incompatible/pkg/streamformatter/streamformatter.go (about)

     1  // Package streamformatter provides helper functions to format a stream.
     2  package streamformatter // import "github.com/docker/docker/pkg/streamformatter"
     3  
     4  import (
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"sync"
     9  
    10  	"github.com/docker/docker/pkg/jsonmessage"
    11  	"github.com/docker/docker/pkg/progress"
    12  )
    13  
    14  const streamNewline = "\r\n"
    15  
    16  type jsonProgressFormatter struct{}
    17  
    18  func appendNewline(source []byte) []byte {
    19  	return append(source, []byte(streamNewline)...)
    20  }
    21  
    22  // FormatStatus formats the specified objects according to the specified format (and id).
    23  func FormatStatus(id, format string, a ...interface{}) []byte {
    24  	str := fmt.Sprintf(format, a...)
    25  	b, err := json.Marshal(&jsonmessage.JSONMessage{ID: id, Status: str})
    26  	if err != nil {
    27  		return FormatError(err)
    28  	}
    29  	return appendNewline(b)
    30  }
    31  
    32  // FormatError formats the error as a JSON object
    33  func FormatError(err error) []byte {
    34  	jsonError, ok := err.(*jsonmessage.JSONError)
    35  	if !ok {
    36  		jsonError = &jsonmessage.JSONError{Message: err.Error()}
    37  	}
    38  	if b, err := json.Marshal(&jsonmessage.JSONMessage{Error: jsonError, ErrorMessage: err.Error()}); err == nil {
    39  		return appendNewline(b)
    40  	}
    41  	return []byte(`{"error":"format error"}` + streamNewline)
    42  }
    43  
    44  func (sf *jsonProgressFormatter) formatStatus(id, format string, a ...interface{}) []byte {
    45  	return FormatStatus(id, format, a...)
    46  }
    47  
    48  // formatProgress formats the progress information for a specified action.
    49  func (sf *jsonProgressFormatter) formatProgress(id, action string, progress *jsonmessage.JSONProgress, aux interface{}) []byte {
    50  	if progress == nil {
    51  		progress = &jsonmessage.JSONProgress{}
    52  	}
    53  	var auxJSON *json.RawMessage
    54  	if aux != nil {
    55  		auxJSONBytes, err := json.Marshal(aux)
    56  		if err != nil {
    57  			return nil
    58  		}
    59  		auxJSON = new(json.RawMessage)
    60  		*auxJSON = auxJSONBytes
    61  	}
    62  	b, err := json.Marshal(&jsonmessage.JSONMessage{
    63  		Status:          action,
    64  		ProgressMessage: progress.String(),
    65  		Progress:        progress,
    66  		ID:              id,
    67  		Aux:             auxJSON,
    68  	})
    69  	if err != nil {
    70  		return nil
    71  	}
    72  	return appendNewline(b)
    73  }
    74  
    75  type rawProgressFormatter struct{}
    76  
    77  func (sf *rawProgressFormatter) formatStatus(id, format string, a ...interface{}) []byte {
    78  	return []byte(fmt.Sprintf(format, a...) + streamNewline)
    79  }
    80  
    81  func (sf *rawProgressFormatter) formatProgress(id, action string, progress *jsonmessage.JSONProgress, aux interface{}) []byte {
    82  	if progress == nil {
    83  		progress = &jsonmessage.JSONProgress{}
    84  	}
    85  	endl := "\r"
    86  	if progress.String() == "" {
    87  		endl += "\n"
    88  	}
    89  	return []byte(action + " " + progress.String() + endl)
    90  }
    91  
    92  // NewProgressOutput returns a progress.Output object that can be passed to
    93  // progress.NewProgressReader.
    94  func NewProgressOutput(out io.Writer) progress.Output {
    95  	return &progressOutput{sf: &rawProgressFormatter{}, out: out, newLines: true}
    96  }
    97  
    98  // NewJSONProgressOutput returns a progress.Output that formats output
    99  // using JSON objects
   100  func NewJSONProgressOutput(out io.Writer, newLines bool) progress.Output {
   101  	return &progressOutput{sf: &jsonProgressFormatter{}, out: out, newLines: newLines}
   102  }
   103  
   104  type formatProgress interface {
   105  	formatStatus(id, format string, a ...interface{}) []byte
   106  	formatProgress(id, action string, progress *jsonmessage.JSONProgress, aux interface{}) []byte
   107  }
   108  
   109  type progressOutput struct {
   110  	sf       formatProgress
   111  	out      io.Writer
   112  	newLines bool
   113  	mu       sync.Mutex
   114  }
   115  
   116  // WriteProgress formats progress information from a ProgressReader.
   117  func (out *progressOutput) WriteProgress(prog progress.Progress) error {
   118  	var formatted []byte
   119  	if prog.Message != "" {
   120  		formatted = out.sf.formatStatus(prog.ID, prog.Message)
   121  	} else {
   122  		jsonProgress := jsonmessage.JSONProgress{Current: prog.Current, Total: prog.Total, HideCounts: prog.HideCounts, Units: prog.Units}
   123  		formatted = out.sf.formatProgress(prog.ID, prog.Action, &jsonProgress, prog.Aux)
   124  	}
   125  
   126  	out.mu.Lock()
   127  	defer out.mu.Unlock()
   128  	_, err := out.out.Write(formatted)
   129  	if err != nil {
   130  		return err
   131  	}
   132  
   133  	if out.newLines && prog.LastUpdate {
   134  		_, err = out.out.Write(out.sf.formatStatus("", ""))
   135  		return err
   136  	}
   137  
   138  	return nil
   139  }
   140  
   141  // AuxFormatter is a streamFormatter that writes aux progress messages
   142  type AuxFormatter struct {
   143  	io.Writer
   144  }
   145  
   146  // Emit emits the given interface as an aux progress message
   147  func (sf *AuxFormatter) Emit(id string, aux interface{}) error {
   148  	auxJSONBytes, err := json.Marshal(aux)
   149  	if err != nil {
   150  		return err
   151  	}
   152  	auxJSON := new(json.RawMessage)
   153  	*auxJSON = auxJSONBytes
   154  	msgJSON, err := json.Marshal(&jsonmessage.JSONMessage{ID: id, Aux: auxJSON})
   155  	if err != nil {
   156  		return err
   157  	}
   158  	msgJSON = appendNewline(msgJSON)
   159  	n, err := sf.Writer.Write(msgJSON)
   160  	if n != len(msgJSON) {
   161  		return io.ErrShortWrite
   162  	}
   163  	return err
   164  }