Skip to content

Commit

Permalink
Add a tep validate helper
Browse files Browse the repository at this point in the history
Split the script into two helpers:
- table that generates the TEP table
- validate that checks if TEPs are well formed (for CI)

Add a docker file for the teps tool.

Signed-off-by: Andrea Frittoli <[email protected]>
  • Loading branch information
afrittoli authored and tekton-robot committed Nov 13, 2020
1 parent 80d0638 commit d1475a7
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 138 deletions.
1 change: 0 additions & 1 deletion hack/requirements.txt

This file was deleted.

127 changes: 0 additions & 127 deletions hack/teps.py

This file was deleted.

14 changes: 4 additions & 10 deletions teps/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,6 @@ The TEP `OWNERS` are the **main** owners of the following projects:

## Merging TEPs

- When creating a new TEP, pick a number for it, equal to the maximum
TEP available in the repo and open PRs.
A valid new number can be obtained via `./hack/tep-number.sh`. Note
that the picked number is not "locked" until a PR is created.
The TEP number shall be set
in the TEP file name, TEP title and PR title:
- file name in the format `teps/<XXXX>-<tep-title>.md`
- title in the format `# TEP-XXXX: <tep-title>`
- PR title in the format `TEP-XXXX: <tep-title>`
- TEP should be merge as soon as possible in the `proposed` state. As
soon as a general consensus is reached that the TEP, as described
(even if incomplete) make sense to pursue, the TEP can be
Expand Down Expand Up @@ -109,12 +100,15 @@ This is the complete list of Tekton teps:
|[TEP-0011](0011-redirecting-step-output-streams.md) | redirecting-step-output-streams | implementable | 2020-11-02 |
|[TEP-0012](0012-api-spec.md) | API Specification | implementable | 2020-08-10 |
|[TEP-0014](0014-step-timeout.md) | Step Timeout | implementable | 2020-09-10 |
|[TEP-0015](0015-pending-pipeline.md) | pending-pipeline-run | implementable | 2020-09-10 |
|[TEP-0016](0016-concise-trigger-bindings.md) | Concise Embedded TriggerBindings | implementable | 2020-09-15 |
|[TEP-0019](0019-other-arch-support.md) | Other Arch Support | proposed | 2020-09-30 |
|[TEP-0020](0020-s390x-support.md) | s390x Support | proposed | 2020-09-21 |
|[TEP-0022](0022-trigger-immutable-input.md) | Triggers - Immutable Input Events | proposed | 2020-09-29 |
|[TEP-0024](0024-embedded-trigger-templates.md) | Embedded TriggerTemplates | implementable | 2020-10-01 |
|[TEP-0025](0025-hermekton.md) | Hermetic Builds | implementable | 2020-09-11 |
|[TEP-0026](0026-interceptor-plugins.md) | interceptor-plugins | implementable | 2020-10-08 |
|[TEP-0027](0028-task-execution-status-at-runtime.md) | task-exec-status-at-runtime | implementable | 2020-11-02 |
|[TEP-0027](0027-https-connection-to-triggers-eventlistener.md) | HTTPS Connection to Triggers EventListener | proposed | 2020-10-19 |
|[TEP-NNNN](XXXX-step-workspaces.md) | step-and-sidecar-workspaces | proposed | 2020-10-02 |
|[TEP-XXXX](XXXX-workspace-paths.md) | workspace-paths | proposed | 2020-10-18 |
|[TEP-XXXX](XXXX-step-workspaces.md) | step-and-sidecar-workspaces | proposed | 2020-10-02 |
22 changes: 22 additions & 0 deletions teps/tools/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright 2020 The Tekton Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

FROM python:3.8-slim

COPY requirements.txt .

RUN pip3 install --no-cache -r requirements.txt
COPY ./teps.py .

CMD [ "python3", "./teps.py" ]
2 changes: 2 additions & 0 deletions teps/tools/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
chevron>=0.13.1
click>=7.1.2
195 changes: 195 additions & 0 deletions teps/tools/teps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
#!/usr/bin/env python

# Copyright 2020 The Tekton Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# This script helps synchronize contents from their respective sources of
# truth (usually GitHub repositories of each Tekton
# components, such as tektoncd/pipelines) to tektoncd/website.

# This scripts provide automation for the TEPs

import json
import logging
import os
import re
import sys
from urllib import parse

import chevron
import click

LOCAL_TEP_FOLDER = os.path.normpath(
os.path.join(os.path.dirname(
os.path.abspath(__file__)), '..'))
README_TEMPLATE = 'README.md.mustache'
README = 'README.md'

# Header and body matches
RE_MATCHERS = {
'title': re.compile(r'^title: (.+)$'),
'status': re.compile(r'^status: ([a-zA-Z]+)$'),
'created': re.compile(r'^creation-date: ([0-9\-\./]+)$'),
'lastupdated': re.compile(r'^last-updated: ([0-9\-\./]+)$'),
'number': re.compile(r'^# (TEP-[0-9]{4}): .*$')
}

# File and body matches
RE_TEP_NUMBER_FILENAME = re.compile(r'([0-9]{4})-.*.md')
RE_TEP_NONUMBER_FILENAME = re.compile(r'([A-Za-z]{4})-.*.md')

EXCLUDED_FILENAMES = set(['README.md',
'README.md.mustache',
'OWNERS'])

class InvalidTep(Exception):
pass


class ValidationErrors(Exception):

def __init__(self, errors):
self.errors = errors
super().__init__()

def __str__(self):
return '\n'.join([str(e) for e in self.errors])


def read_tep(tep_io, ignore_errors=False):
""" Read a TEP and validate its format
:param tep: a TextIO with the TEP content and a name
:param ignore_errors: return a tep dict even in case of errors
:returns: a tuple (dict, list). If the tep is not valid, and
ignore_errors==True, the list includes all Errors encountered.
"""
issues = []

filename = os.path.normpath(tep_io.name)
_, tep_name = os.path.split(filename)
tep_match = RE_TEP_NUMBER_FILENAME.match(tep_name)
tep_file_number = None
if not tep_match:
issues.append(InvalidTep(
f'TEP filenames should match /^[0-9]{4}-/. Found: {tep_name}'))
else:
# Get a TEP number from the files name
tep_file_number = int(tep_match.groups()[0])

tep = dict(link = tep_name)
for i, line in enumerate(tep_io):
# With one author, the title should be in L10
# Allow for a long list of authors and some padding
if i > 30:
break
# Try to match with all expected fields on this line
for k, regexp in RE_MATCHERS.items():
_match = regexp.match(line)
if _match:
# If we already found this, log a warning
# and ignore the new value
if tep.get(k):
issues.append(
InvalidTep(f'{key} found more than once in {filename}'))
else:
tep[k] = _match.groups()[0]
# If we had a match, continue on the next line
break

# Some post-processing to handle missing fields
tep_number = tep.get('number')
tep_file_number = f'TEP-{tep_file_number:04d}' if tep_file_number is not None else 'TEP-XXXX'
if not tep_number:
issues.append(
InvalidTep(f'No TEP number title (# TEP-NNNN) in {filename}'))
tep['number'] = tep_file_number
elif tep_file_number != tep_number:
issues.append(InvalidTep(
f'TEP number {tep_file_number} from filename does '
f'not match TEP number {tep_number} from title '
f'(# TEP-NNNN) in {filename}'))
# Set last updated to creation date if missing
if not tep.get('lastupdated'):
tep['lastupdated'] = tep.get('created')

if issues and not ignore_errors:
raise ValidationErrors(issues)

return tep, issues


def tep_from_file(tep_filename, ignore_errors=False):
""" returns a TEP dict for valid input files or None """

with open(tep_filename, 'r') as tep_file:
tep, issues = read_tep(tep_file, ignore_errors=ignore_errors)

if issues:
logging.warning(f'{issues}')

return tep


def teps_in_folder(teps_folder):
return [f for f in os.listdir(LOCAL_TEP_FOLDER) if os.path.isfile(
os.path.join(LOCAL_TEP_FOLDER, f)) and f not in EXCLUDED_FILENAMES]


@click.group()
def teps():
pass

@teps.command()
@click.option('--teps-folder', default=LOCAL_TEP_FOLDER,
help='the folder that contains the TEP files')
def table(teps_folder):
if not os.path.isdir(teps_folder):
logging.error(f'Invalid TEP folder {teps_folder}')
sys.exit(1)
teps = dict(teps = [])
tep_files = teps_in_folder(teps_folder)
for tep_file in tep_files:
tep = tep_from_file(os.path.join(LOCAL_TEP_FOLDER, tep_file),
ignore_errors=True)
if tep:
teps['teps'].append(tep)

# Sort by TEP number
teps['teps'] = sorted(teps['teps'], key=lambda k: k['number'])
with open(os.path.join(LOCAL_TEP_FOLDER, README_TEMPLATE), 'r') as template:
with open(os.path.join(LOCAL_TEP_FOLDER, README), 'w+') as readme:
readme.write(chevron.render(template, teps))

@teps.command()
@click.option('--teps-folder', default=LOCAL_TEP_FOLDER,
help='the folder that contains the TEP files')
def validate(teps_folder):
if not os.path.isdir(teps_folder):
logging.error(f'Invalid TEP folder {teps_folder}')
sys.exit(1)
errors =[]
tep_files = teps_in_folder(teps_folder)

for tep_file in tep_files:
try:
tep_from_file(os.path.join(LOCAL_TEP_FOLDER, tep_file))
except ValidationErrors as ve:
errors.append(ve)
if errors:
logging.error('\n'.join([str(e) for e in errors]))
sys.exit(1)

if __name__ == '__main__':
teps()

0 comments on commit d1475a7

Please sign in to comment.