github.com/GoogleCloudPlatform/testgrid@v0.0.174/metadata/job.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 metadata
    18  
    19  import (
    20  	"strings"
    21  )
    22  
    23  // Started holds the started.json values of the build.
    24  type Started struct {
    25  	// Timestamp is UTC epoch seconds when the job started.
    26  	Timestamp int64 `json:"timestamp"` // epoch seconds
    27  	// Node holds the name of the machine that ran the job.
    28  	Node string `json:"node,omitempty"`
    29  
    30  	// Consider whether to keep the following:
    31  
    32  	// Pull holds the PR number the primary repo is testing
    33  	Pull string `json:"pull,omitempty"`
    34  	// Repos holds the RepoVersion of all commits checked out.
    35  	Repos      map[string]string `json:"repos,omitempty"` // {repo: branch_or_pull} map
    36  	RepoCommit string            `json:"repo-commit,omitempty"`
    37  
    38  	// Deprecated fields:
    39  
    40  	// Metadata is deprecated, add to finished.json
    41  	Metadata Metadata `json:"metadata,omitempty"` // TODO(fejta): remove
    42  
    43  	// Use RepoCommit
    44  	DeprecatedJobVersion  string `json:"job-version,omitempty"`  // TODO(fejta): remove
    45  	DeprecatedRepoVersion string `json:"repo-version,omitempty"` // TODO(fejta): remove
    46  
    47  }
    48  
    49  const (
    50  	// JobVersion is the metadata key that overrides repo-commit in Started when set.
    51  	JobVersion = "job-version"
    52  )
    53  
    54  // Finished holds the finished.json values of the build
    55  type Finished struct {
    56  	// Timestamp is UTC epoch seconds when the job finished.
    57  	// An empty value indicates an incomplete job.
    58  	Timestamp *int64 `json:"timestamp,omitempty"`
    59  	// Passed is true when the job completes successfully.
    60  	Passed *bool `json:"passed"`
    61  	// Metadata holds data computed by the job at runtime.
    62  	// For example, the version of a binary downloaded at runtime
    63  	// The JobVersion key overrides the auto-version set in Started.
    64  	Metadata Metadata `json:"metadata,omitempty"`
    65  
    66  	// Consider whether to keep the following:
    67  
    68  	// Deprecated fields:
    69  
    70  	// Result is deprecated, use Passed.
    71  	Result string `json:"result,omitempty"` // TODO(fejta): remove
    72  
    73  	// Use Metadata[JobVersion] or Started.RepoCommit
    74  	DeprecatedJobVersion  string `json:"job-version,omitempty"`  // TODO(fejta): remove
    75  	DeprecatedRevision    string `json:"revision,omitempty"`     // TODO(fejta): remove
    76  	DeprecatedRepoVersion string `json:"repo-version,omitempty"` // TODO(fejta): remove
    77  }
    78  
    79  // Metadata holds the finished.json values in the metadata key.
    80  //
    81  // Metadata values can either be string or string map of strings
    82  //
    83  // TODO(fejta): figure out which of these we want and document them
    84  // Special values: infra-commit, repos, repo, repo-commit, links, others
    85  type Metadata map[string]interface{}
    86  
    87  // String returns the name key if its value is a string, and true if the key is present.
    88  func (m Metadata) String(name string) (*string, bool) {
    89  	if v, ok := m[name]; !ok {
    90  		return nil, false
    91  	} else if t, good := v.(string); !good {
    92  		return nil, true
    93  	} else {
    94  		return &t, true
    95  	}
    96  }
    97  
    98  // Meta returns the name key if its value is a child object, and true if they key is present.
    99  func (m Metadata) Meta(name string) (*Metadata, bool) {
   100  	if v, ok := m[name]; !ok {
   101  		return nil, false
   102  	} else if t, good := v.(Metadata); good {
   103  		return &t, true
   104  	} else if t, good := v.(map[string]interface{}); good {
   105  		child := Metadata(t)
   106  		return &child, true
   107  	}
   108  	return nil, true
   109  }
   110  
   111  // Keys returns an array of the keys of all valid Metadata values.
   112  func (m Metadata) Keys() []string {
   113  	ka := make([]string, 0, len(m))
   114  	for k := range m {
   115  		if _, ok := m.Meta(k); ok {
   116  			ka = append(ka, k)
   117  		}
   118  	}
   119  	return ka
   120  }
   121  
   122  // Strings returns the submap of values in the map that are strings.
   123  func (m Metadata) Strings() map[string]string {
   124  	bm := map[string]string{}
   125  	for k, v := range m {
   126  		if s, ok := v.(string); ok {
   127  			bm[k] = s
   128  		}
   129  		// TODO(fejta): handle sub items
   130  	}
   131  	return bm
   132  }
   133  
   134  // MultiString get list of strings if exist, and true if they key is present.
   135  // If value is list of strings we return it as is.
   136  // If value is list of interfaces, we try convert it into list of strings, if fail we return an empty list
   137  func (m Metadata) MultiString(name string) ([]string, bool) {
   138  	if v, ok := m[name]; !ok {
   139  		return []string{}, false
   140  	} else if lstStr, good := v.([]string); good {
   141  		return lstStr, true
   142  	} else if lstInter, good := v.([]interface{}); good {
   143  		convertedStrings := []string{}
   144  		for _, inter := range lstInter {
   145  			s, good := inter.(string)
   146  			if !good {
   147  				return []string{}, true
   148  			}
   149  			convertedStrings = append(convertedStrings, s)
   150  		}
   151  		return convertedStrings, true
   152  	}
   153  	return []string{}, true
   154  }
   155  
   156  // firstFilled returns the first non-empty option or else def.
   157  func firstFilled(def string, options ...string) string {
   158  	for _, o := range options {
   159  		if o != "" {
   160  			return o
   161  		}
   162  	}
   163  	return def
   164  }
   165  
   166  // Missing is the key for a missing version.
   167  const Missing = "missing"
   168  
   169  // Version extracts the job's custom version or else the checked out repo commit.
   170  func Version(started Started, finished Finished) string {
   171  	// TODO(fejta): started.RepoCommit, finished.Metadata.String(JobVersion)
   172  	meta := func(key string) string {
   173  		if finished.Metadata == nil {
   174  			return ""
   175  		}
   176  		v, ok := finished.Metadata.String(key)
   177  		if !ok {
   178  			return ""
   179  		}
   180  		return *v
   181  	}
   182  
   183  	val := firstFilled(
   184  		Missing,
   185  		finished.DeprecatedJobVersion, started.DeprecatedJobVersion,
   186  		started.DeprecatedRepoVersion, finished.DeprecatedRepoVersion,
   187  		meta("revision"), meta("repo-commit"),
   188  		meta(JobVersion), started.RepoCommit, // TODO(fejta): remove others
   189  	)
   190  	parts := strings.SplitN(val, "+", 2)
   191  	val = parts[len(parts)-1]
   192  	if n := len(val); n > 9 {
   193  		return val[:9]
   194  	}
   195  	return val
   196  }
   197  
   198  // SetVersion ensures that the repoCommit and jobVersion are set appropriately.
   199  func SetVersion(started *Started, finished *Finished, repoCommit, jobVersion string) {
   200  	if started != nil && repoCommit != "" {
   201  		started.DeprecatedRepoVersion = repoCommit // TODO(fejta): pump this
   202  		started.RepoCommit = repoCommit
   203  	}
   204  	if finished != nil && jobVersion != "" {
   205  		if finished.Metadata == nil {
   206  			finished.Metadata = Metadata{}
   207  		}
   208  		finished.Metadata["job-version"] = jobVersion
   209  		finished.DeprecatedJobVersion = jobVersion
   210  	}
   211  }