k8s.io/test-infra@v0.0.0-20240520184403-27c6b4c223d8/gopherage/cmd/metadata/metadata.go (about)

     1  /*
     2  Copyright 2021 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 metadata
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/json"
    22  	"errors"
    23  	"fmt"
    24  	"io"
    25  	"os"
    26  	"os/exec"
    27  	"strings"
    28  
    29  	"github.com/spf13/cobra"
    30  )
    31  
    32  const (
    33  	// From https://github.com/kubernetes/test-infra/blob/master/prow/jobs.md#job-environment-variables
    34  	repo_owner = "REPO_OWNER"
    35  )
    36  
    37  type Flags struct {
    38  	outputFile    string
    39  	host          string
    40  	project       string
    41  	workspaceRoot string
    42  	traceType     string
    43  	commitID      string
    44  	ref           string
    45  	source        string
    46  	replace       string
    47  	patchSet      string
    48  	changeNum     string
    49  }
    50  
    51  type Metadata interface{}
    52  
    53  type gitRunner func(...string) (string, error)
    54  type envFetcher func(string) string
    55  
    56  type CoverageMetadata struct {
    57  	Host      string `json:"host"`
    58  	Project   string `json:"project"`
    59  	Root      string `json:"workspace_root"`
    60  	TraceType string `json:"trace_type"`
    61  }
    62  
    63  type AbsMetadata struct {
    64  	CoverageMetadata
    65  	CommitID    string `json:"commit_id"`
    66  	Ref         string `json:"ref"`
    67  	Source      string `json:"source"`
    68  	ReplaceRoot string `json:"git_project"`
    69  }
    70  
    71  type IncMetadata struct {
    72  	CoverageMetadata
    73  	ChangeNum string `json:"changelist_num"`
    74  	PatchSet  string `json:"patchset_num"`
    75  }
    76  
    77  // MakeCommand returns a `junit` command.
    78  func MakeCommand() *cobra.Command {
    79  	Flags := &Flags{}
    80  	baseCmd := &cobra.Command{
    81  		Use:   "metadata [...fields]",
    82  		Short: "Produce json file containing metadata about coverage collection.",
    83  		Long:  `Builds a json file containing information about the repo .`,
    84  		Run: func(cmd *cobra.Command, args []string) {
    85  			fmt.Println("Sub command required [inc, abs]")
    86  			os.Exit(1)
    87  		},
    88  	}
    89  
    90  	absCmd := &cobra.Command{
    91  		Use:   "abs [...fields]",
    92  		Short: "Build abs metadata file.",
    93  		Long:  "Produce json file containing metadata about absremental coverage collection.",
    94  		Run: func(cmd *cobra.Command, args []string) {
    95  			ValidateBase(Flags, cmd, os.Getenv)
    96  			ValidateAbs(Flags, gitCommand)
    97  			metadata := &AbsMetadata{
    98  				CoverageMetadata: CoverageMetadata{
    99  					Host:      Flags.host,
   100  					Project:   Flags.project,
   101  					Root:      Flags.workspaceRoot,
   102  					TraceType: Flags.traceType,
   103  				},
   104  				CommitID:    Flags.commitID,
   105  				Ref:         Flags.ref,
   106  				Source:      Flags.source,
   107  				ReplaceRoot: Flags.replace,
   108  			}
   109  			WriteJson(Flags, metadata)
   110  		},
   111  	}
   112  	absCmd.Flags().StringVarP(&Flags.outputFile, "output", "o", "-", "output file")
   113  	absCmd.Flags().StringVarP(&Flags.host, "host", "", "", "Name of repo host")
   114  	absCmd.Flags().StringVarP(&Flags.project, "project", "p", "", "Project name")
   115  	absCmd.Flags().StringVarP(&Flags.workspaceRoot, "root", "w", "", "path to workspace root of repo")
   116  	absCmd.Flags().StringVarP(&Flags.traceType, "trace", "t", "COV", "type of coverage [COV, LCOV]")
   117  	absCmd.Flags().StringVarP(&Flags.commitID, "commit", "c", "", "Current Commit Hash (git rev-parse HEAD)")
   118  	absCmd.Flags().StringVarP(&Flags.ref, "ref", "r", "", "Current branch ref (git branch --show-current).")
   119  	absCmd.Flags().StringVarP(&Flags.source, "source", "s", "", "custom field for information about coverage source")
   120  	absCmd.Flags().StringVarP(&Flags.replace, "replace_root", "", "", "path to replace root of coverage paths with")
   121  
   122  	incCmd := &cobra.Command{
   123  		Use:   "inc [...fields]",
   124  		Short: "Build inc metadata file.",
   125  		Long:  "Produce json file containing metadata about incremental coverage collection.",
   126  		Run: func(cmd *cobra.Command, args []string) {
   127  			ValidateBase(Flags, cmd, os.Getenv)
   128  			ValidateInc(Flags)
   129  			metadata := &IncMetadata{
   130  				CoverageMetadata: CoverageMetadata{
   131  					Host:      Flags.host,
   132  					Project:   Flags.project,
   133  					Root:      Flags.workspaceRoot,
   134  					TraceType: Flags.traceType,
   135  				},
   136  				ChangeNum: Flags.changeNum,
   137  				PatchSet:  Flags.patchSet,
   138  			}
   139  			WriteJson(Flags, metadata)
   140  		},
   141  	}
   142  	incCmd.Flags().StringVarP(&Flags.outputFile, "output", "o", "-", "output file")
   143  	incCmd.Flags().StringVarP(&Flags.host, "host", "", "", "Name of repo host")
   144  	incCmd.Flags().StringVarP(&Flags.project, "project", "p", "", "Project name")
   145  	incCmd.Flags().StringVarP(&Flags.workspaceRoot, "root", "w", "", "path to workspace root of repo")
   146  	incCmd.Flags().StringVarP(&Flags.traceType, "trace", "t", "COV", "type of coverage [COV, LCOV]")
   147  	incCmd.Flags().StringVarP(&Flags.changeNum, "changelist_num", "", "", "Gerrit change number")
   148  	incCmd.Flags().StringVarP(&Flags.patchSet, "patchset_num", "", "", "Gerrit Patchset Number")
   149  
   150  	baseCmd.AddCommand(incCmd, absCmd)
   151  
   152  	return baseCmd
   153  }
   154  
   155  func gitCommand(args ...string) (string, error) {
   156  	cmd := exec.Command("git", args...)
   157  	var out bytes.Buffer
   158  	cmd.Stdout = &out
   159  
   160  	err := cmd.Run()
   161  	if err != nil {
   162  		return "", err
   163  	}
   164  	return strings.TrimSuffix(out.String(), "\n"), nil
   165  }
   166  
   167  func ValidateBase(Flags *Flags, cmd *cobra.Command, env envFetcher) error {
   168  	if Flags.project == "" {
   169  		project := env(repo_owner)
   170  		if project == "" {
   171  			cmd.Usage()
   172  			return fmt.Errorf("Failed to collect project from ENV: (%s) not found", repo_owner)
   173  		}
   174  		Flags.project = project
   175  	}
   176  
   177  	return nil
   178  }
   179  
   180  func ValidateInc(Flags *Flags) error {
   181  	if Flags.changeNum == "" {
   182  		return errors.New("Gerrit change number is required")
   183  	}
   184  
   185  	if Flags.patchSet == "" {
   186  		return errors.New("Gerrit patchset number is required")
   187  	}
   188  	return nil
   189  }
   190  
   191  func ValidateAbs(Flags *Flags, r gitRunner) error {
   192  	if Flags.commitID == "" {
   193  		commit, err := r("rev-parse", "HEAD")
   194  		if err != nil {
   195  			return fmt.Errorf("Failed to fetch Commit Hash from within covered repo: %v.", err)
   196  		}
   197  		Flags.commitID = commit
   198  	}
   199  
   200  	if Flags.ref == "" {
   201  		ref, err := r("branch", "--show-current")
   202  
   203  		if err != nil {
   204  			fmt.Fprintf(os.Stderr, "Failed to fetch ref from within covered repo: %v. Defaulting to HEAD\n", err)
   205  			ref = "HEAD"
   206  		}
   207  		Flags.ref = ref
   208  	}
   209  	return nil
   210  }
   211  
   212  func WriteJson(Flags *Flags, m Metadata) error {
   213  	var file io.WriteCloser
   214  
   215  	j, err := json.Marshal(m)
   216  
   217  	if err != nil {
   218  		return fmt.Errorf("Failed to build json: %v.", err)
   219  	}
   220  
   221  	if Flags.outputFile == "-" {
   222  		file = os.Stdout
   223  	} else {
   224  		file, err = os.Create(Flags.outputFile)
   225  		if err != nil {
   226  			return fmt.Errorf("Failed to create file: %v.", err)
   227  		}
   228  	}
   229  	if _, err := file.Write(j); err != nil {
   230  		return fmt.Errorf("Failed to write json: %v.", err)
   231  	}
   232  	file.Close()
   233  	return nil
   234  }