def check_required_properties(d, path, required): if not isinstance(d, dict): raise ValidationError(path, "must be an object") for key in required: if key not in d: raise ValidationError(path, "property '%s' is required" % key)
def parse_jobs(e, path): if not isinstance(e, list): raise ValidationError(path, "must be an array") for i in range(0, len(e)): elem = e[i] p = "%s[%s]" % (path, i) if 'type' not in elem: raise ValidationError(p, "does not contain a 'type'") t = elem['type'] if t == 'git': parse_git(elem, p) elif t == 'wait': parse_wait(elem, p) elif t == 'workflow': parse_workflow(elem, p) elif t == 'docker': parse_docker(elem, p) elif t == 'docker-image': parse_docker_image(elem, p) elif t == 'docker-compose': parse_docker_compose(elem, p) else: raise ValidationError(p, "type '%s' not supported" % t)
def check_allowed_properties(d, path, allowed): if not isinstance(d, dict): raise ValidationError(path, "must be an object") for key in d: if key not in allowed: raise ValidationError(path, "invalid property '%s'" % key)
def parse_table(d, path): check_allowed_properties(d, path, ("type", "rows", "headers")) check_required_properties(d, path, ("type", "rows")) if 'headers' in d: if not isinstance(d['headers'], list): raise ValidationError(path + ".headers", "must be an array") col_count = len(d['headers']) if col_count == 0: raise ValidationError(path + ".headers", "must not be empty") for i in range(0, col_count): h = d['headers'][i] parse_text(h, "%s.headers[%s]" % (path, i)) if not isinstance(d['rows'], list): raise ValidationError(path + ".rows", "must be an array") if not d['rows']: raise ValidationError(path + ".rows", "must not be empty") for i in range(0, len(d['rows'])): r = d['rows'][i] p = "%s.rows[%s]" % (path, i) if 'headers' in d: if len(r) != col_count: raise ValidationError( p, "does not have the correct number of columns") parse_elements(r, p)
def parse_services(d, path): if not isinstance(d, list): raise ValidationError(path, "must be an array") names = [] for i in range(0, len(d)): elem = d[i] p = "%s[%s]" % (path, i) check_allowed_properties(elem, p, ("apiVersion", "kind", "metadata", "spec")) check_required_properties(elem, p, ("apiVersion", "kind", "metadata")) check_required_properties(elem['metadata'], p + ".metadata", ("name", )) name = elem['metadata']['name'] if name in names: raise ValidationError(p, "duplicate service name found: %s" % name) names.append(name) if 'spec' in elem: parse_service_spec(elem['spec'], p + ".spec")
def parse_secret_ref(value, p): if not isinstance(value, dict): raise ValidationError(p, "must be an object") if "$secret" not in value: raise ValidationError(p, "must contain a $secret") check_text(value['$secret'], p + ".$secret")
def check_allowed_properties(d, path, allowed): if not isinstance(d, dict): raise ValidationError(path, "must be an object") for key in d: if key not in allowed: if 'name' in d.keys(): raise ValidationError('%s(%s)' % (path, d['name']), "invalid property '%s'" % key) else: raise ValidationError(path, "invalid property '%s'" % key)
def parse_ts(e, path): if not isinstance(e, list): raise ValidationError(path, "must be an array") if not e: raise ValidationError(path, "must not be empty") for i in range(0, len(e)): elem = e[i] path = "%s[%s]" % (path, i) parse_t(elem, path)
def check_string_array(e, path): if not isinstance(e, list): raise ValidationError(path, "must be an array") if not e: raise ValidationError(path, "must not be empty") for i in range(0, len(e)): elem = e[i] path = "%s[%s]" % (path, i) check_text(elem, path)
def check_required_properties(d, path, required): if not isinstance(d, dict): raise ValidationError(path, "must be an object") for key in required: if key not in d: if 'name' in d.keys(): raise ValidationError('%s(%s)' % (path, d['name']), "property '%s' is required" % key) else: raise ValidationError(path, "property '%s' is required" % key)
def parse_limits(d, path): check_allowed_properties(d, path, ("memory", "cpu")) check_required_properties(d, path, ("memory", "cpu")) check_int_or_float(d['cpu'], path + ".cpu") check_number(d['memory'], path + ".memory") if d['cpu'] <= 0.3: raise ValidationError(path + ".cpu", "must be greater than 0.3") if d['memory'] <= 255: raise ValidationError(path + ".memory", "must be greater than 255")
def parse_grid(d, path): check_allowed_properties(d, path, ("type", "rows")) check_required_properties(d, path, ("type", "rows")) if not isinstance(d['rows'], list): raise ValidationError(path + ".rows", "must be an array") if not d['rows']: raise ValidationError(path + ".rows", "must not be empty") for i in range(0, len(d['rows'])): r = d['rows'][i] parse_elements(r, "%s.rows[%s]" % (path, i))
def validate_json(d): parse_document(d) if 'jobs' not in d: return True jobs = {} for i in range(0, len(d['jobs'])): job = d['jobs'][i] job_name = job['name'] path = "#jobs[%s]" % i if jobs.get(job_name, None): raise ValidationError(path + ".name", "Job name '%s' already exists" % job_name) if job_name == 'Create Jobs': raise ValidationError(path + ".name", "Job name may not be 'Create Jobs'") jobs[job_name] = job if 'depends_on' not in job: continue deps = {} for depends_on in job['depends_on']: parent_name = None if isinstance(depends_on, dict): parent_name = depends_on['job'] else: parent_name = depends_on if job_name == parent_name: raise ValidationError( path, "Job '%s' may not depend on itself" % parent_name) if parent_name not in jobs: raise ValidationError(path + ".depends_on", "Job '%s' not found" % parent_name) if parent_name in deps: raise ValidationError( path + ".depends_on", "'%s' duplicate dependencies" % parent_name) deps[parent_name] = True return True
def parse_environment(e, path): if not isinstance(e, dict): raise ValidationError(path, "must be an object") for key in e: value = e[key] p = path + "." + key if isinstance(value, dict): parse_secret_ref(value, p) else: try: check_text(value, p) except: raise ValidationError(p, "must be a string or object")
def parse_vault_ref(value, p): if not isinstance(value, dict): raise ValidationError(p, "must be an object") if "$vault" not in value: raise ValidationError(p, "must contain a $vault") check_text(value['$vault'], p + ".$vault") if "$vault_secret_path" not in value: raise ValidationError(p, "must contain a $vault_secret_path") check_text(value['$vault_secret_path'], p + ".$vault_secret_path") if "$vault_secret_key" not in value: raise ValidationError(p, "must contain a $vault_secret_key") check_text(value['$vault_secret_key'], p + ".$vault_secret_key")
def parse_docker(d, path): check_allowed_properties(d, path, ("type", "name", "docker_file", "depends_on", "resources", "build_only", "security", "commit_after_run", "keep", "environment", "build_arguments", "deployments")) check_required_properties(d, path, ("type", "name", "docker_file", "resources")) check_name(d['name'], path + ".name") check_text(d['docker_file'], path + ".docker_file") parse_resources(d['resources'], path + ".resources") if 'build_only' in d: check_boolean(d['build_only'], path + ".build_only") if 'keep' in d: check_boolean(d['keep'], path + ".keep") if 'depends_on' in d: check_name_array(d['depends_on'], path + ".depends_on") if 'security' in d: parse_security(d['security'], path + ".security") if 'commit_after_run' in d: if not isinstance(d['commit_after_run'], bool): raise ValidationError(path + ".commit_after_run", "Must be boolean") if 'environment' in d: parse_environment(d['environment'], path + ".environment") if 'build_arguments' in d: parse_build_args(d['build_arguments'], path + ".build_arguments") if 'deployments' in d: parse_deployments(d['deployments'], path + ".deployments")
def parse_document(d): check_allowed_properties(d, "#", ("version", "jobs", "generator")) check_required_properties(d, "#", ("version", )) check_version(d['version'], "#version") if 'generator' not in d and 'jobs' not in d: raise ValidationError("#", "Either 'jobs' or 'generator' must be set") if 'generator' in d and 'jobs' in d: raise ValidationError("#", "Either 'jobs' or 'generator' must be set, not both") if 'jobs' in d: parse_jobs(d['jobs'], "#jobs") if 'generator' in d: parse_generator(d['generator'], "#generator")
def parse_depends_on(a, path): if not isinstance(a, list): raise ValidationError(path, "must be an list") if not a: raise ValidationError(path, "must not be empty") for i in range(0, len(a)): n = a[i] p = '[%s]' % i if isinstance(n, dict): # contains conditions parse_depends_on_condition(n, path + p) else: # no conditions, default to 'finished' check_name(n, path + p)
def parse_build_args(e, path): if not isinstance(e, dict): raise ValidationError(path, "must be an object") for key in e: value = e[key] p = path + "." + key check_text(value, p)
def parse_deployments(e, path): if not isinstance(e, list): raise ValidationError(path, "must be an array") if not e: raise ValidationError(path, "must not be empty") for i in range(0, len(e)): elem = e[i] p = "%s[%s]" % (path, i) if 'type' not in elem: raise ValidationError(p, "does not contain a 'type'") t = elem['type'] if t == 'docker-registry': parse_deployment_docker_registry(elem, p) else: raise ValidationError(p, "type '%s' not supported" % t)
def parse_text(d, path): check_allowed_properties(d, path, ("type", "text", "emphasis", "color")) check_required_properties(d, path, ("type", "text")) check_text(d['text'], path + ".text") if 'emphasis' in d: if d['emphasis'] not in ("bold", "italic"): raise ValidationError(path + ".emphasis", "not a valid value") if 'color' in d: check_color(d['color'], path + ".color")
def parse_depends_on_condition(d, path): check_allowed_properties(d, path, ("job", "on")) check_required_properties(d, path, ("job", "on")) check_name(d['job'], path + '.job') on = d['on'] if not isinstance(on, list): raise ValidationError(path + ".on", "must be a list") if not on: raise ValidationError(path + ".on", "must not be empty") on_used = {} for i in on: if i not in ('finished', 'error', 'failure', 'unstable', '*'): raise ValidationError(path + ".on", "%s is not a valid value" % i) if i in on_used: raise ValidationError(path + ".on", "%s used twice" % i) on_used[i] = True
def parse_elements(e, path): if not isinstance(e, list): raise ValidationError(path, "must be an array") if not e: raise ValidationError(path, "must not be empty") for i in range(0, len(e)): elem = e[i] p = "%s[%s]" % (path, i) if 'type' not in elem: raise ValidationError(p, "does not contain a 'type'") t = elem['type'] if t == 'h1' or t == 'h2' or t == 'h3' or t == 'h4' or t == 'h5': parse_heading(elem, p) elif t == 'hline': parse_hline(elem, p) elif t == 'paragraph': parse_paragraph(elem, p) elif t == 'text': parse_text(elem, p) elif t == 'ordered_list': parse_ordered_list(elem, p) elif t == 'unordered_list': parse_unordered_list(elem, p) elif t == 'group': parse_group(elem, p) elif t == 'pie': parse_pie(elem, p) elif t == 'grid': parse_grid(elem, p) elif t == 'table': parse_table(elem, p) elif t == 'icon': parse_icon(elem, p) else: raise ValidationError(p, "type '%s' not supported" % t)
def check_color(d, path): if d not in ("red", "green", "blue", "yellow", "orange", "white", "black", "grey"): raise ValidationError(path, "not a valid value")
def check_text(t, path, allowEmpty=False): if not isinstance(t, str): raise ValidationError(path, "is not a string") if not allowEmpty and not t: raise ValidationError(path, "empty string not allowed")
def check_version(v, path): if not isinstance(v, int): raise ValidationError(path, "must be an int") if v != 1: raise ValidationError(path, "unsupported version")
def validate_json(d): parse_document(d) if 'jobs' not in d: return True jobs = {} all_job_names = set([j['name'] for j in d['jobs']]) all_deps = {} for i in range(0, len(d['jobs'])): job = d['jobs'][i] job_name = job['name'] path = "#jobs[%s]" % i if jobs.get(job_name, None): raise ValidationError(path + ".name", "Job name '%s' already exists" % job_name) if job_name == 'Create Jobs': raise ValidationError(path + ".name", "Job name may not be 'Create Jobs'") jobs[job_name] = job if 'depends_on' not in job: continue deps = {} for depends_on in job['depends_on']: parent_name = None if isinstance(depends_on, dict): parent_name = depends_on['job'] else: parent_name = depends_on if job_name == parent_name: raise ValidationError( path, "Job '%s' may not depend on itself" % parent_name) if parent_name not in all_job_names: raise ValidationError(path + ".depends_on", "Job '%s' not found" % parent_name) if parent_name in deps: raise ValidationError( path + ".depends_on", "'%s' duplicate dependencies" % parent_name) deps[parent_name] = True if deps: all_deps[job_name] = deps for job_name, deps in all_deps.items(): queue = list(deps.keys()) for dep_job in queue: if dep_job == job_name: raise ValidationError("Jobs", "Circular dependency detected.") if dep_job in all_deps: queue.extend(all_deps[dep_job].keys()) return True
def check_number(d, path): if not isinstance(d, int): raise ValidationError(path, "must be a number")
def check_boolean(d, path): if not isinstance(d, bool): raise ValidationError(path, "must be a boolean")
def check_name(n, path): check_text(n, path) if not special_match(n): raise ValidationError(path, "'%s' not a valid value" % n)