summaryrefslogtreecommitdiff
path: root/scripts/validate-lorries
blob: 0d1d880d5a3b9a3051039009bbc46ab09e00cb01 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
#!/usr/bin/python3

import glob
import json
import sys
import yaml


def check_string(v):
    if not isinstance(v, str):
        return 'must be a string'


def check_bool(v):
    if not isinstance(v, bool):
        return 'must be a boolean'


def check_type_string(v):
    err = check_string(v)
    if err is not None:
        return err

    if v not in ['bzr', 'cvs', 'git', 'gzip', 'hg', 'svn', 'tarball', 'zip']:
        return '"%s" is not a recognised type' % v


def check_stringlist(v):
    if not isinstance(v, list):
        return 'must be a list'

    for elem in v:
        if not isinstance(elem, str):
            return 'must have strings as elements'


def check_stringdict(v):
    if not isinstance(v, dict):
        return 'must be a dictionary'

    for key, value in v.items():
        if not (isinstance(key, str) and isinstance(value, str)):
            return 'must have strings as keys and value'


def check_svn_layout(v):
    if v == 'standard':
        return

    if not isinstance(v, dict):
        return 'must be either "standard" or a dictionary'

    return check_stringdict(v)


def validate(filename, repo_filenames, strict=True):
    is_ok = True
    repo_name = None

    def diagnostic(level, msg):
        if repo_name is None:
            print('%s: %s: %s' % (level, filename, msg),
                  file=sys.stderr)
        else:
            print('%s: %s: %s: %s' % (level, filename, repo_name, msg),
                  file=sys.stderr)

    def error(msg):
        nonlocal is_ok
        is_ok = False
        diagnostic('E', msg)

    def warning(msg):
        diagnostic('W', msg)

    with open(filename) as f:
        try:
            try:
                obj = yaml.safe_load(f)
            except yaml.YAMLError:
                f.seek(0)
                obj = json.load(f)
        except ValueError:
            error('not valid YAML or JSON')
            return is_ok

        if not isinstance(obj, dict):
            error('must be a dictionary')
            return is_ok

        for repo_name, upstream_def in obj.items():
            if repo_name in repo_filenames:
                error('repository already defined in %s'
                      % repo_filenames[repo_name])
            else:
                repo_filenames[repo_name] = filename
            if not strict:
                continue

            upstream_type = upstream_def.get('type')

            value_checkers = {
                # Keys listed in Lorry's README
                'type':               check_type_string,
                'url':                check_string,
                'check-certificates': check_bool,
                'branches':           check_stringdict,
                'layout':             check_svn_layout,
                'module':             check_string,
                # Undocumented Lorry feature
                'refspecs':           check_stringlist,
                # Lorry Controller extension
                'description':        check_string,
            }

            required_keys = set(['type'])
            optional_keys = set(['refspecs', 'description'])
            if upstream_type != 'bzr':
                required_keys.add('url')
            else:
                optional_keys.add('url')
                optional_keys.add('branches')
            if upstream_type in ['bzr', 'git', 'hg']:
                optional_keys.add('check-certificates')
            if upstream_type == 'svn':
                required_keys.add('layout')
            if upstream_type == 'cvs':
                required_keys.add('module')

            for key in required_keys:
                if key not in upstream_def:
                    error('missing "%s" key' % key)

            # For bzr, exactly one of url and branches keys is required
            if upstream_type == 'bzr':
                has_url = 'url' in upstream_def
                has_branches = 'branches' in upstream_def
                if has_url and has_branches:
                    error('has both "url" and "branches" keys')
                elif not has_url and not has_branches:
                    error('missing both "url" and "branches" keys')

            for key, value in upstream_def.items():
                if key.startswith('x-products-'):
                    # Baserock Import extension
                    msg = check_stringlist(value)
                else:
                    if key not in required_keys and key not in optional_keys:
                        warning('unexpected "%s" key' % key)
                    if key in value_checkers:
                        msg = value_checkers[key](value)
                    else:
                        msg = None
                if msg:
                    error('%s: %s' % (key, msg))

    return is_ok


def main():
    repo_filenames = {}
    all_ok = True

    for filename in glob.glob('*-lorries/*.lorry'):
        if not validate(filename, repo_filenames):
            all_ok = False

    for filename in glob.glob('*-lorries-disabled/*.lorry'):
        if not validate(filename, repo_filenames, strict=False):
            all_ok = False

    sys.exit(0 if all_ok else 1)


if __name__ == '__main__':
    main()