github.com/shashidharatd/test-infra@v0.0.0-20171006011030-71304e1ca560/gubernator/filters.py (about) 1 # Copyright 2016 The Kubernetes Authors. 2 # 3 # Licensed under the Apache License, Version 2.0 (the "License"); 4 # you may not use this file except in compliance with the License. 5 # You may obtain a copy of the License at 6 # 7 # http://www.apache.org/licenses/LICENSE-2.0 8 # 9 # Unless required by applicable law or agreed to in writing, software 10 # distributed under the License is distributed on an "AS IS" BASIS, 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 # See the License for the specific language governing permissions and 13 # limitations under the License. 14 15 import logging 16 import datetime 17 import hashlib 18 import os 19 import re 20 import time 21 import urllib 22 23 import jinja2 24 25 26 GITHUB_VIEW_TEMPLATE = 'https://github.com/%s/blob/%s/%s#L%s' 27 GITHUB_COMMIT_TEMPLATE = 'https://github.com/%s/commit/%s' 28 LINKIFY_RE = re.compile( 29 r'(^\s*/\S*/)(kubernetes/(\S+):(\d+)(?: \+0x[0-9a-f]+)?)$', 30 flags=re.MULTILINE) 31 32 33 def do_timestamp(unix_time, css_class='timestamp', tmpl='%F %H:%M'): 34 """Convert an int Unix timestamp into a human-readable datetime.""" 35 t = datetime.datetime.utcfromtimestamp(unix_time) 36 return jinja2.Markup('<span class="%s" data-epoch="%s">%s</span>' % 37 (css_class, unix_time, t.strftime(tmpl))) 38 39 40 def do_dt_to_epoch(dt): 41 return time.mktime(dt.timetuple()) 42 43 44 def do_shorttimestamp(unix_time): 45 t = datetime.datetime.utcfromtimestamp(unix_time) 46 return jinja2.Markup('<span class="shorttimestamp" data-epoch="%s">%s</span>' % 47 (unix_time, t.strftime('%d %H:%M'))) 48 49 50 def do_duration(seconds): 51 """Convert a numeric duration in seconds into a human-readable string.""" 52 hours, seconds = divmod(seconds, 3600) 53 minutes, seconds = divmod(seconds, 60) 54 if hours: 55 return '%dh%dm' % (hours, minutes) 56 if minutes: 57 return '%dm%ds' % (minutes, seconds) 58 else: 59 if seconds < 10: 60 return '%.2fs' % seconds 61 return '%ds' % seconds 62 63 64 def do_slugify(inp): 65 """Convert an arbitrary string into a url-safe slug.""" 66 inp = re.sub(r'[^\w\s-]+', '', inp) 67 return re.sub(r'\s+', '-', inp).lower() 68 69 70 def do_linkify_stacktrace(inp, commit, repo): 71 """Add links to a source code viewer for every mentioned source line.""" 72 inp = unicode(jinja2.escape(inp)) 73 if not commit: 74 return jinja2.Markup(inp) # this was already escaped, mark it safe! 75 def rep(m): 76 prefix, full, path, line = m.groups() 77 return '%s<a href="%s">%s</a>' % ( 78 prefix, 79 GITHUB_VIEW_TEMPLATE % (repo, commit, path, line), 80 full) 81 return jinja2.Markup(LINKIFY_RE.sub(rep, inp)) 82 83 84 def do_github_commit_link(commit, repo): 85 commit_url = jinja2.escape(GITHUB_COMMIT_TEMPLATE % (repo, commit)) 86 return jinja2.Markup('<a href="%s">%s</a>' % (commit_url, commit[:8])) 87 88 89 def do_testcmd(name): 90 if name.startswith('k8s.io/'): 91 try: 92 pkg, name = name.split(' ') 93 except ValueError: # don't block the page render 94 logging.error('Unexpected Go unit test name %r', name) 95 return name 96 return 'go test -v %s -run %s$' % (pkg, name) 97 elif name.startswith('//'): 98 return 'bazel test %s' % name 99 else: 100 name = re.sub(r'^\[k8s\.io\] ', '', name) 101 name_escaped = re.escape(name).replace('\\ ', '\\s') 102 103 test_args = ('--ginkgo.focus=%s$' % name_escaped) 104 return "go run hack/e2e.go -v -test --test_args='%s'" % test_args 105 106 107 def do_parse_pod_name(text): 108 """Find the pod name from the failure and return the pod name.""" 109 p = re.search(r' pod (\S+)', text) 110 if p: 111 return re.sub(r'[\'"\\:]', '', p.group(1)) 112 else: 113 return "" 114 115 116 def do_label_attr(labels, name): 117 ''' 118 >> do_label_attr(['needs-rebase', 'size/XS'], 'size') 119 'XS' 120 ''' 121 name += '/' 122 for label in labels: 123 if label.startswith(name): 124 return label[len(name):] 125 return '' 126 127 def do_classify_size(payload): 128 ''' 129 Determine the size class for a PR, based on either its labels or 130 on the magnitude of its changes. 131 ''' 132 size = do_label_attr(payload['labels'], 'size') 133 if not size and 'additions' in payload and 'deletions' in payload: 134 lines = payload['additions'] + payload['deletions'] 135 # based on mungegithub/mungers/size.go 136 for limit, label in [ 137 (10, 'XS'), 138 (30, 'S'), 139 (100, 'M'), 140 (500, 'L'), 141 (1000, 'XL') 142 ]: 143 if lines < limit: 144 return label 145 return 'XXL' 146 return size 147 148 149 def do_render_status(payload, user): 150 states = set() 151 152 text = 'Pending' 153 if 'lgtm' in payload.get('labels', []): 154 text = 'LGTM' 155 elif user in payload.get('attn', {}): 156 text = payload['attn'][user].title() 157 if '#' in text: # strip start/end attn timestamps 158 text = text[:text.index('#')] 159 160 for ctx, (state, _url, desc) in payload.get('status', {}).items(): 161 if ctx == 'Submit Queue' and state == 'pending': 162 if 'does not have lgtm' in desc.lower(): 163 # Don't show overall status as pending when Submit 164 # won't continue without LGTM. 165 continue 166 if ctx == 'code-review/reviewable' and state == 'pending': 167 # Reviewable isn't a CI, so we don't care if it's pending. 168 # Its dashboard might replace all of this eventually. 169 continue 170 states.add(state) 171 172 icon = '' 173 if 'failure' in states: 174 icon = 'x' 175 state = 'failure' 176 elif 'pending' in states: 177 icon = 'primitive-dot' 178 state = 'pending' 179 elif 'success' in states: 180 icon = 'check' 181 state = 'success' 182 if icon: 183 icon = '<span class="text-%s octicon octicon-%s"></span>' % ( 184 state, icon) 185 return jinja2.Markup('%s%s' % (icon, text)) 186 187 188 def do_get_latest(payload, user): 189 text = payload.get('attn', {}).get(user) 190 if not text: 191 return None 192 if '#' not in text: 193 return None 194 _text, _start, latest = text.rsplit('#', 2) 195 return float(latest) 196 197 198 def do_ltrim(s, needle): 199 if s.startswith(needle): 200 return s[len(needle):] 201 return s 202 203 204 def do_select(seq, pred): 205 return filter(pred, seq) 206 207 208 def do_tg_url(testgrid_query, test_name=''): 209 if test_name: 210 regex = '^Overall$|' + re.escape(test_name) 211 testgrid_query += '&include-filter-by-regex=%s' % urllib.quote(regex) 212 return 'https://k8s-testgrid.appspot.com/%s' % testgrid_query 213 214 215 def do_gcs_browse_url(gcs_path): 216 if not gcs_path.endswith('/'): 217 gcs_path += '/' 218 return 'http://gcsweb.k8s.io/gcs' + gcs_path 219 220 221 static_hashes = {} 222 223 def do_static(filename): 224 filename = 'static/%s' % filename 225 if filename not in static_hashes: 226 data = open(filename).read() 227 static_hashes[filename] = hashlib.sha1(data).hexdigest()[:10] 228 return '/%s?%s' % (filename, static_hashes[filename]) 229 230 231 do_basename = os.path.basename 232 do_dirname = os.path.dirname 233 do_quote_plus = urllib.quote_plus 234 235 236 def register(filters): 237 """Register do_* functions in this module in a dictionary.""" 238 for name, func in globals().items(): 239 if name.startswith('do_'): 240 filters[name[3:]] = func