github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/pkg/toolrecord/toolrecord.go (about)

     1  package toolrecord
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"io/fs"
     8  	"os"
     9  	"path/filepath"
    10  	"time"
    11  )
    12  
    13  type keydataset struct {
    14  	Name        string // technical name from the tool
    15  	Value       string // technical value
    16  	DisplayName string // user friendly name - optional
    17  	URL         string // direct URL to navigate to this key in the tool backend - optional
    18  }
    19  
    20  // Toolrecord holds all data to locate a tool result
    21  // in the tool's backend
    22  type Toolrecord struct {
    23  	RecordVersion int
    24  
    25  	ToolName     string
    26  	ToolInstance string
    27  
    28  	// tool agnostic convenience aggregations
    29  	// picks the most specific URL + concatenate the dimension names
    30  	// for easy dashboard / xls creation
    31  	DisplayName string
    32  	DisplayURL  string
    33  
    34  	fileUtils fileWriteUtils
    35  
    36  	// detailed keydata - needs tool-specific parsing
    37  	Keys []keydataset
    38  
    39  	// place for additional context information
    40  	Context map[string]interface{}
    41  
    42  	// internal - not exported to the json
    43  	workspace      string
    44  	reportFileName string
    45  }
    46  
    47  type fileWriteUtils interface {
    48  	MkdirAll(path string, perm fs.FileMode) error
    49  	WriteFile(name string, data []byte, perm fs.FileMode) error
    50  }
    51  
    52  // New - initialize a new toolrecord
    53  func New(fileUtils fileWriteUtils, workspace, toolName, toolInstance string) *Toolrecord {
    54  	tr := Toolrecord{}
    55  	tr.fileUtils = fileUtils
    56  
    57  	tr.RecordVersion = 1
    58  	tr.ToolName = toolName
    59  	tr.ToolInstance = toolInstance
    60  	tr.Keys = []keydataset{}
    61  	tr.Context = make(map[string]interface{})
    62  
    63  	tr.workspace = workspace
    64  
    65  	reportFileName := filepath.Join(workspace,
    66  		"toolruns",
    67  		"toolrun_"+toolName+"_all.json")
    68  	tr.reportFileName = reportFileName
    69  
    70  	// keep a timestamp inside all files
    71  	now := time.Now().UTC()
    72  	var otr = &tr
    73  	otr.AddContext("generatedOnUtc", now.Format("20060102150405"))
    74  	return otr
    75  }
    76  
    77  // AddKeyData - add one key to the current toolrecord
    78  // calls must follow the tool's hierachy ( e.g. org -> project)
    79  // as DisplayName & DisplayURL are based on the call sequence
    80  func (tr *Toolrecord) AddKeyData(keyname, keyvalue, displayname, url string) error {
    81  	if keyname == "" {
    82  		return errors.New("TR_ADD_KEY: empty keyname")
    83  	}
    84  	if keyvalue == "" {
    85  		return fmt.Errorf("TR_ADD_KEY: empty keyvalue for %v", keyname)
    86  	}
    87  	keydata := keydataset{Name: keyname, Value: keyvalue, DisplayName: displayname, URL: url}
    88  	tr.Keys = append(tr.Keys, keydata)
    89  	return nil
    90  }
    91  
    92  // AddContext - add additional context information
    93  // second call with the same label will overwrite the first call's data
    94  func (tr *Toolrecord) AddContext(label string, data interface{}) error {
    95  	if label == "" {
    96  		return errors.New("TR_ADD_CONTEXT: no label supplied")
    97  	}
    98  	tr.Context[label] = data
    99  	return nil
   100  }
   101  
   102  // GetFileName - local filename for the current record
   103  func (tr *Toolrecord) GetFileName() string {
   104  	return tr.reportFileName
   105  }
   106  
   107  // Persist - write the current record to file system
   108  func (tr *Toolrecord) Persist() error {
   109  	if tr.workspace == "" {
   110  		return errors.New("TR_PERSIST: empty workspace ")
   111  	}
   112  	if tr.ToolName == "" {
   113  		return errors.New("TR_PERSIST: empty toolName")
   114  	}
   115  	if tr.ToolInstance == "" {
   116  		return errors.New("TR_PERSIST: empty instanceName")
   117  	}
   118  	// create workspace/toolrecord
   119  	dirPath := filepath.Join(tr.workspace, "toolruns")
   120  	err := tr.fileUtils.MkdirAll(dirPath, os.ModePerm)
   121  	if err != nil {
   122  		return fmt.Errorf("TR_PERSIST: %v", err)
   123  	}
   124  
   125  	// set default display data if required
   126  	if tr.DisplayName == "" {
   127  		tr.GenerateDefaultDisplayData()
   128  	}
   129  
   130  	file, err := json.Marshal(tr)
   131  	if err != nil {
   132  		return fmt.Errorf("TR_PERSIST: %v", err)
   133  	}
   134  	// no json generated ?
   135  	if len(file) == 0 {
   136  		return fmt.Errorf("TR_PERSIST: empty json content")
   137  	}
   138  	err = tr.fileUtils.WriteFile(tr.GetFileName(), file, 0o644)
   139  	if err != nil {
   140  		return fmt.Errorf("TR_PERSIST: %v", err)
   141  	}
   142  	return nil
   143  }
   144  
   145  // GenerateDefaultDisplayData - default aggregation for overall displayName and URL
   146  // can be overriden by calling SetOverallDisplayData
   147  func (tr *Toolrecord) GenerateDefaultDisplayData() {
   148  	displayName := ""
   149  	displayURL := ""
   150  	for _, keyset := range tr.Keys {
   151  		// create "name1 - name2 - name3"
   152  		subDisplayName := keyset.DisplayName
   153  		if subDisplayName != "" {
   154  			if displayName != "" {
   155  				displayName = displayName + " - "
   156  			}
   157  			displayName = displayName + subDisplayName
   158  		}
   159  		subURL := keyset.URL
   160  		if subURL != "" {
   161  			displayURL = subURL
   162  		}
   163  	}
   164  	tr.DisplayName = displayName
   165  	tr.DisplayURL = displayURL
   166  }
   167  
   168  // SetOverallDisplayData - override the default generation for DisplayName & DisplayURL
   169  func (tr *Toolrecord) SetOverallDisplayData(newName, newURL string) {
   170  	tr.DisplayName = newName
   171  	tr.DisplayURL = newURL
   172  }