github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/spyglass/podlogartifact.go (about)

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package spyglass
    18  
    19  import (
    20  	"bytes"
    21  	"errors"
    22  	"fmt"
    23  	"io"
    24  	"net/url"
    25  	"strings"
    26  
    27  	"k8s.io/test-infra/prow/kube"
    28  	"k8s.io/test-infra/prow/spyglass/lenses"
    29  )
    30  
    31  type jobAgent interface {
    32  	GetProwJob(job string, id string) (kube.ProwJob, error)
    33  	GetJobLog(job string, id string) ([]byte, error)
    34  	GetJobLogTail(job string, id string, n int64) ([]byte, error)
    35  }
    36  
    37  // PodLogArtifact holds data for reading from a specific pod log
    38  type PodLogArtifact struct {
    39  	name      string
    40  	buildID   string
    41  	sizeLimit int64
    42  	jobAgent
    43  }
    44  
    45  var (
    46  	errInsufficientJobInfo = errors.New("insufficient job information provided")
    47  	errInvalidSizeLimit    = errors.New("sizeLimit must be a 64-bit integer greater than 0")
    48  )
    49  
    50  // NewPodLogArtifact creates a new PodLogArtifact
    51  func NewPodLogArtifact(jobName string, buildID string, sizeLimit int64, ja jobAgent) (*PodLogArtifact, error) {
    52  	if jobName == "" {
    53  		return nil, errInsufficientJobInfo
    54  	}
    55  	if buildID == "" {
    56  		return nil, errInsufficientJobInfo
    57  	}
    58  	if sizeLimit < 0 {
    59  		return nil, errInvalidSizeLimit
    60  	}
    61  	return &PodLogArtifact{
    62  		name:      jobName,
    63  		buildID:   buildID,
    64  		sizeLimit: sizeLimit,
    65  		jobAgent:  ja,
    66  	}, nil
    67  }
    68  
    69  // CanonicalLink returns a link to where pod logs are streamed
    70  func (a *PodLogArtifact) CanonicalLink() string {
    71  	q := url.Values{
    72  		"job": []string{a.name},
    73  		"id":  []string{a.buildID},
    74  	}
    75  	u := url.URL{
    76  		Path:     "/log",
    77  		RawQuery: q.Encode(),
    78  	}
    79  	return u.String()
    80  }
    81  
    82  // JobPath gets the path within the job for the pod log. Always returns build-log.txt.
    83  // This is because the pod log becomes the build log after the job artifact uploads
    84  // are complete, which should be used instead of the pod log.
    85  func (a *PodLogArtifact) JobPath() string {
    86  	return "build-log.txt"
    87  }
    88  
    89  // ReadAt implements reading a range of bytes from the pod logs endpoint
    90  func (a *PodLogArtifact) ReadAt(p []byte, off int64) (n int, err error) {
    91  	logs, err := a.jobAgent.GetJobLog(a.name, a.buildID)
    92  	if err != nil {
    93  		return 0, fmt.Errorf("error getting pod log: %v", err)
    94  	}
    95  	r := bytes.NewReader(logs)
    96  	readBytes, err := r.ReadAt(p, off)
    97  	if err == io.EOF {
    98  		return readBytes, io.EOF
    99  	}
   100  	if err != nil {
   101  		return 0, fmt.Errorf("error reading pod logs: %v", err)
   102  	}
   103  	return readBytes, nil
   104  }
   105  
   106  // ReadAll reads all available pod logs, failing if they are too large
   107  func (a *PodLogArtifact) ReadAll() ([]byte, error) {
   108  	size, err := a.Size()
   109  	if err != nil {
   110  		return nil, fmt.Errorf("error getting pod log size: %v", err)
   111  	}
   112  	if size > a.sizeLimit {
   113  		return nil, lenses.ErrFileTooLarge
   114  	}
   115  	logs, err := a.jobAgent.GetJobLog(a.name, a.buildID)
   116  	if err != nil {
   117  		return nil, fmt.Errorf("error getting pod log: %v", err)
   118  	}
   119  	return logs, nil
   120  }
   121  
   122  // ReadAtMost reads at most n bytes
   123  func (a *PodLogArtifact) ReadAtMost(n int64) ([]byte, error) {
   124  	logs, err := a.jobAgent.GetJobLog(a.name, a.buildID)
   125  	if err != nil {
   126  		return nil, fmt.Errorf("error getting pod log: %v", err)
   127  	}
   128  	reader := bytes.NewReader(logs)
   129  	var byteCount int64
   130  	var p []byte
   131  	for byteCount < n {
   132  		b, err := reader.ReadByte()
   133  		if err == io.EOF {
   134  			return p, io.EOF
   135  		}
   136  		if err != nil {
   137  			return nil, fmt.Errorf("error reading pod log: %v", err)
   138  		}
   139  		p = append(p, b)
   140  		byteCount++
   141  	}
   142  	return p, nil
   143  }
   144  
   145  // ReadTail reads the last n bytes of the pod log
   146  func (a *PodLogArtifact) ReadTail(n int64) ([]byte, error) {
   147  	logs, err := a.jobAgent.GetJobLogTail(a.name, a.buildID, n)
   148  	if err != nil {
   149  		return nil, fmt.Errorf("error getting pod log tail: %v", err)
   150  	}
   151  	size := int64(len(logs))
   152  	var off int64
   153  	if n > size {
   154  		off = 0
   155  	} else {
   156  		off = size - n
   157  	}
   158  	p := make([]byte, n)
   159  	readBytes, err := bytes.NewReader(logs).ReadAt(p, off)
   160  	if err != nil && err != io.EOF {
   161  		return nil, fmt.Errorf("error reading pod log tail: %v", err)
   162  	}
   163  	return p[:readBytes], nil
   164  }
   165  
   166  // Size gets the size of the pod log. Note: this function makes the same network call as reading the entire file.
   167  func (a *PodLogArtifact) Size() (int64, error) {
   168  	logs, err := a.jobAgent.GetJobLog(a.name, a.buildID)
   169  	if err != nil {
   170  		return 0, fmt.Errorf("error getting size of pod log: %v", err)
   171  	}
   172  	return int64(len(logs)), nil
   173  
   174  }
   175  
   176  // isProwJobSource returns true if the provided string is a valid Prowjob source and false otherwise
   177  func isProwJobSource(src string) bool {
   178  	return strings.HasPrefix(src, "prowjob/")
   179  }