github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/pkg/spyglass/lenses/coverage/coverage.go (about)

     1  /*
     2  Copyright 2019 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 coverage provides a coverage viewer for Spyglass
    18  package coverage
    19  
    20  import (
    21  	"bytes"
    22  	"compress/gzip"
    23  	"encoding/base64"
    24  	"encoding/json"
    25  	"fmt"
    26  	"html/template"
    27  	"path/filepath"
    28  	"strings"
    29  
    30  	"github.com/sirupsen/logrus"
    31  
    32  	"sigs.k8s.io/prow/pkg/config"
    33  	"sigs.k8s.io/prow/pkg/spyglass/api"
    34  	"sigs.k8s.io/prow/pkg/spyglass/lenses"
    35  )
    36  
    37  const (
    38  	name     = "coverage"
    39  	title    = "Coverage"
    40  	priority = 7
    41  )
    42  
    43  func init() {
    44  	lenses.RegisterLens(Lens{})
    45  }
    46  
    47  // Lens is the implementation of a coverage-rendering Spyglass lens.
    48  type Lens struct{}
    49  
    50  // Config returns the lens's configuration.
    51  func (lens Lens) Config() lenses.LensConfig {
    52  	return lenses.LensConfig{
    53  		Name:     name,
    54  		Title:    title,
    55  		Priority: priority,
    56  	}
    57  }
    58  
    59  // Header renders the content of <head> from template.html.
    60  func (lens Lens) Header(artifacts []api.Artifact, resourceDir string, config json.RawMessage, spyglassConfig config.Spyglass) string {
    61  	t, err := template.ParseFiles(filepath.Join(resourceDir, "template.html"))
    62  	if err != nil {
    63  		return fmt.Sprintf("<!-- FAILED LOADING HEADER: %v -->", err)
    64  	}
    65  	var buf bytes.Buffer
    66  	if err := t.ExecuteTemplate(&buf, "header", nil); err != nil {
    67  		return fmt.Sprintf("<!-- FAILED EXECUTING HEADER TEMPLATE: %v -->", err)
    68  	}
    69  	return buf.String()
    70  }
    71  
    72  // Callback does nothing.
    73  func (lens Lens) Callback(artifacts []api.Artifact, resourceDir string, data string, config json.RawMessage, spyglassConfig config.Spyglass) string {
    74  	return ""
    75  }
    76  
    77  // Body renders the <body>
    78  func (lens Lens) Body(artifacts []api.Artifact, resourceDir string, data string, config json.RawMessage, spyglassConfig config.Spyglass) string {
    79  	if len(artifacts) == 0 {
    80  		logrus.Error("coverage Body() called with no artifacts, which should never happen.")
    81  		return "Why am I here? There is no coverage file."
    82  	}
    83  
    84  	profileArtifact := artifacts[0]
    85  	var htmlArtifact api.Artifact
    86  	if len(artifacts) > 1 {
    87  		if len(artifacts) > 2 {
    88  			return "Too many files - expected one coverage file and one optional HTML file"
    89  		}
    90  		if strings.HasSuffix(artifacts[0].JobPath(), ".html") {
    91  			htmlArtifact = artifacts[0]
    92  			profileArtifact = artifacts[1]
    93  		} else if strings.HasSuffix(artifacts[1].JobPath(), ".html") {
    94  			htmlArtifact = artifacts[1]
    95  			profileArtifact = artifacts[0]
    96  		} else {
    97  			return "Multiple input files, but none had a .html extension."
    98  		}
    99  	}
   100  
   101  	content, err := profileArtifact.ReadAll()
   102  	if err != nil {
   103  		logrus.WithError(err).Warn("Couldn't read a coverage file that should exist.")
   104  		return fmt.Sprintf("Faiiled to read the coverage file: %v", err)
   105  	}
   106  
   107  	coverageTemplate, err := template.ParseFiles(filepath.Join(resourceDir, "template.html"))
   108  	if err != nil {
   109  		logrus.WithError(err).Error("Error executing template.")
   110  		return fmt.Sprintf("Failed to load template file: %v", err)
   111  	}
   112  
   113  	w := &bytes.Buffer{}
   114  	g := gzip.NewWriter(w)
   115  	_, err = g.Write(content)
   116  	if err != nil {
   117  		logrus.WithError(err).Warn("Failed to compress coverage file")
   118  		return fmt.Sprintf("Failed to compress coverage file: %v", err)
   119  	}
   120  	if err := g.Close(); err != nil {
   121  		logrus.WithError(err).Warn("Failed to close gzip for coverage file")
   122  		return fmt.Sprintf("Failed to close gzip for coverage file: %v", err)
   123  	}
   124  	result := base64.StdEncoding.EncodeToString(w.Bytes())
   125  
   126  	renderedCoverageURL := ""
   127  	if htmlArtifact != nil {
   128  		renderedCoverageURL = htmlArtifact.CanonicalLink()
   129  	}
   130  	t := struct {
   131  		CoverageContent  string
   132  		RenderedCoverage string
   133  	}{
   134  		CoverageContent:  result,
   135  		RenderedCoverage: renderedCoverageURL,
   136  	}
   137  	var buf bytes.Buffer
   138  	if err := coverageTemplate.ExecuteTemplate(&buf, "body", t); err != nil {
   139  		logrus.WithError(err).Error("Error executing template.")
   140  	}
   141  
   142  	return buf.String()
   143  }