diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fb0dcc9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,87 @@ +*.py[cod] + +# C extensions +*.so + +# Packages +.Python +*.egg +*.egg-info +dist +build +eggs +env +parts +bin +var +sdist +develop-eggs +.installed.cfg +lib +lib64 + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +htmlcov/ +.coverage +.tox +nosetests.xml +.testrepository +.cache +coverage.xml + +# Translations +*.mo +*.pot + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# Complexity +output/*.html +output/*/index.html + +# Sphinx +doc/build + +# pbr generates these +AUTHORS +ChangeLog + +# Editors +*~ +.*.swp +/.idea + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# Build artifacts +/.mypy_cache +/.pytest_cache +/artifacts/test/*.xml + diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..2f75e99 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,457 @@ +[MASTER] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns= + +# Pickle collected data for later comparisons. +persistent=yes + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +# load-plugins=pylint_django + +# Use multiple processes to speed up Pylint. +jobs=1 + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code +extension-pkg-whitelist= + +# Allow optimization of some AST trees. This will activate a peephole AST +# optimizer, which will apply various small optimizations. For instance, it can +# be used to obtain the result of joining multiple strings with the addition +# operator. Joining a lot of strings can lead to a maximum recursion error in +# Pylint and this flag can prevent that. It has one side effect, the resulting +# AST will be different than the one from reality. This option is deprecated +# and it will be removed in Pylint 2.0. +optimize-ast=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +confidence=HIGH, INFERENCE, INFERENCE_FAILURE + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +#disable=W,C,R,E0633,E1101,E1136 +# Disabled Warnings Descriptions +# C - +# C0103 - Invalid name +# C0111 - Missing Docstring +# C0411 - Wrong Import Order +# C0412 - Ungrouped imports +# E - All errors +# E0633 - Unpacking non sequence +# E1101 - No member +# E1136 - Unscriptable object +# R - +# R0101 - To many nested blocks +# R0102 - Simplifiable if statement +# R0201 - No self use +# R0204 - Redefined variable type +# R0801 - Duplicate Code +# R0901 - To many ancestors +# R0902 - To many instance attributes +# R0903 - To few public methods +# R0904 - To many public methods +# R0911 - To many return statements +# R0912 - To many branches +# R0913 - To many arguments +# R0914 - To many locals +# R0915 - To many statements +# W - All Warnings +# W0611 - Unused import +# W1401 - Anomaous backslash in string + +# Disabled due to false positives when used with django +# E0633 X +# E1101 X +# E1136 X + +# I0011 + +# Disabled to make lint pass, need to be enabled and fixed over time +# W +# C +# R0201 +# R0204 +# R0801 +# R0901 +# R0902 +# R0903 +# R0904 +# R0912 X +# R0914 X +# R0915 X +disable=W,C,R,I,E0633,E1101,E1136,W0621,C0103 + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +# enable= + + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html. You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=parseable + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". This option is deprecated +# and it will be removed in Pylint 2.0. +files-output=no + +# Tells whether to display a full report or only the messages +reports=no + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details +#msg-template= + + +[BASIC] + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Include a hint for the correct naming format with invalid-name +include-naming-hint=no + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +property-classes=abc.abstractproperty + +# Regular expression matching correct function names +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for function names +function-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for variable names +variable-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct constant names +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Naming hint for constant names +const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression matching correct attribute names +attr-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for attribute names +attr-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for argument names +argument-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct class attribute names +class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Naming hint for class attribute names +class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Regular expression matching correct inline iteration names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Naming hint for inline iteration names +inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ + +# Regular expression matching correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Naming hint for class names +class-name-hint=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression matching correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Naming hint for module names +module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression matching correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for method names +method-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + + +[ELIF] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=6 + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=512 + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma,dict-separator + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + + +[LOGGING] + +# Logging modules to check that the string format arguments are in logging +# function parameter format +logging-modules=logging + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + + +[SPELLING] + +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[TYPECHECK] + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the name of dummy variables (i.e. expectedly +# not used). +dummy-variables-rgx=(_+[a-zA-Z0-9]*?$)|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_,_cb + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,future.builtins + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.* + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branches=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of boolean expressions in a if statement +max-bool-expr=5 + + +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception \ No newline at end of file diff --git a/README.md b/README.md index 22d9cab..9d2a249 100644 --- a/README.md +++ b/README.md @@ -18,20 +18,24 @@ This purpose of this project is to manage security vulnerabilities for open sour ## Install -This script requires Python3 to run, so ensure you have this installed first. Installation of this script is as simple as the following: +This script requires Python 3.5 or newer to run, so ensure you have this installed first. +Installation of this script is as simple as the following: -`git clone https://github.com/yahoo/GitHub-Security-Alerts-Workflow.git $$ cd GitHub-Security-Alerts-Workflow` +```console +pip install https://github.com/yahoo/GitHub-Security-Alerts-Workflow/archive/master.tar.gz +``` ## Usage Use the following command to run this script: -`python3 graph_ql.py graph_ql_authorization_key jira_authorization_key jira_url jira_project_key` +`graph_ql.py graph_ql_authorization_key jira_authorization_key jira_url jira_project_key vulnerabilities_issue_created_track_path` * graph_ql_authorization_key - A GitHub GraphQL access token that has the ability to view security alerts for the chosen repo. * jira_authorization_key - An authorization key for your Jira instance with the ability to create and modify tickets. * jira_url - The endpoint for your Jira instance's issue API, e.g. https://jira.xyz.com/rest/api/2/issue/ * jira_project_key - The identifier key for the Jira project you want to create issues for. +* vulnerabilities_issue_created_track_path - Issue file to create ## Contribute @@ -44,5 +48,3 @@ Ashley Wolf: awolf@verizonmedia.com ## License This project is licensed under the terms of the [Apache 2.0](LICENSE-Apache-2.0) open source license. Please refer to [LICENSE](LICENSE) for the full terms. - - diff --git a/arguments.py b/arguments.py deleted file mode 100644 index 4c85c7f..0000000 --- a/arguments.py +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright 2019, Oath Inc. -# Licensed under the terms of the Apache 2.0 license. See LICENSE file in project root for terms. - - -import sys - -graphql_authorization = sys.argv[1] -jira_authorization = sys.argv[2] -jira_url = sys.argv[3] -jira_project_key = sys.argv[4] -vulnerabilities_issue_created_track_path = sys.argv[5] diff --git a/artifacts/test/.report b/artifacts/test/.report new file mode 100644 index 0000000..e69de29 diff --git a/constants.py b/constants.py deleted file mode 100644 index 77bfec3..0000000 --- a/constants.py +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright 2019, Oath Inc. -# Licensed under the terms of the Apache 2.0 license. See LICENSE file in project root for terms. - -from arguments import jira_url -from arguments import jira_authorization -from arguments import graphql_authorization - - - -headers_graphql = { - 'Accept': 'application/vnd.github.vixen-preview', - 'Authorization': 'bearer %s' % (graphql_authorization), -} - -headers_jira = { - 'Content-Type': 'application/json', - 'Authorization': 'Basic %s' %(jira_authorization) -} - -graphql_url = "https://api.github.com/graphql" - -jira_url = "%s" %(jira_url) - - diff --git a/screwdriver.yaml b/screwdriver.yaml new file mode 100644 index 0000000..a8327cb --- /dev/null +++ b/screwdriver.yaml @@ -0,0 +1,41 @@ +version: 4 +shared: + environment: + PACKAGE_DIR: src/github_security_alerts + +jobs: + validate_test: + template: python/validate_unittest + environment: + TOX_ARGS: -p all --parallel-live + requires: [~commit, ~pr] + + validate_lint: + template: python/validate_lint + requires: [~commit, ~pr] + + validate_codestyle: + template: python/validate_codestyle + requires: [~commit, ~pr] + + validate_safetydb: + template: python/validate_safety + requires: [~commit, ~pr] + + validate_security: + template: python/validate_security + requires: [~commit, ~pr] + + generate_version: + template: python/generate_version + requires: [~commit] + + publish_test_pypi: + template: python/package_python + environment: + # Note, this step will not publish unless the TEST_PYPI_USER and TEST_PYPI_PASSWORD secrets are added to the screwdriver pipeline and + # the Publish variable below is set to True + PUBLISH: False + TWINE_REPOSITORY_URL: https://test.pypi.org/legacy/ + requires: [validate_test, validate_lint, validate_codestyle, validate_safetydb, validate_security, generate_version] + diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..3521b11 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,59 @@ +# Copyright 2019, Oath Inc. +# Licensed under the terms of the Apache 2.0 license. See LICENSE file in project root for terms. +[metadata] +author = Verizon Media Python Platform Team +author_email = 254983+dwighthubbard@users.noreply.github.com +classifiers = + Development Status :: 5 - Production/Stable + Intended Audience :: Developers + License :: OSI Approved :: Apache Software License + Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 +description = This script is for teams that want to leverage GitHub Security Alerts into their workflow +keywords = python3 github security-alerts +license = Apache License +license_file = LICENSE +long_description = file:README.md +long_description_content_type = text/markdown +name = github_security_alerts +url = https://github.com/yahoo/GitHub-Security-Alerts-Workflow +version = 0.0.0 + +[options] +install_requires = + requests + +packages= + github_security_alerts + +package_dir= + =src + +python_requires = >="3.5" + +scripts = + src/github_security_alerts/graph_ql.py + +[options.extras_require] +test = + pytest + pytest-cov + vcrpy-unittest + +[options.entry_points] +console_scripts = + graph_ql.py = github_security_alerts.cli:main + +[mypy] +ignore_missing_imports = True + +[pycodestyle] +count = False +ignore = W291 +max-line-length = 160 +statistics = True + +[sdv4.version] +version_plugin = SDV4_BUILD_NUMBER diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..e8aa484 --- /dev/null +++ b/setup.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +# Copyright 2019, Oath Inc. +# Licensed under the terms of the Apache 2.0 license. See LICENSE file in project root for terms. +import sys +import setuptools + + +def setuptools_version_supported(): + major, minor, patch = setuptools.__version__.split('.') + if int(major) > 31: + return True + return False + + +if __name__ == '__main__': + if not setuptools_version_supported(): + print('Setuptools version 31.0.0 or higher is needed to install this package') + sys.exit(1) + + # We're being run from the command line so call setup with our arguments + setuptools.setup() diff --git a/src/github_security_alerts/__init__.py b/src/github_security_alerts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/github_security_alerts/arguments.py b/src/github_security_alerts/arguments.py new file mode 100644 index 0000000..c738868 --- /dev/null +++ b/src/github_security_alerts/arguments.py @@ -0,0 +1,22 @@ +# Copyright 2019, Oath Inc. +# Licensed under the terms of the Apache 2.0 license. See LICENSE file in project root for terms. +import argparse +import sys + + +def parse_arguments() -> argparse.Namespace: + """ + Parse command line arguments + """ + print(sys.argv) + parser = argparse.ArgumentParser() + parser.add_argument('graphql_authorization') + parser.add_argument('jira_authorization') + parser.add_argument('jira_url') + parser.add_argument('jira_project_key') + parser.add_argument('vulnerabilities_issue_created_track_path') + return parser.parse_args() + + +if __name__ == '__main__': + print(parse_arguments()) diff --git a/src/github_security_alerts/cli.py b/src/github_security_alerts/cli.py new file mode 100644 index 0000000..cc9b4cc --- /dev/null +++ b/src/github_security_alerts/cli.py @@ -0,0 +1,6 @@ +# Copyright 2019, Oath Inc. +# Licensed under the terms of the Apache 2.0 license. See LICENSE file in root for terms. + + +def main(): + from . import graph_ql diff --git a/src/github_security_alerts/constants.py b/src/github_security_alerts/constants.py new file mode 100644 index 0000000..828f9e9 --- /dev/null +++ b/src/github_security_alerts/constants.py @@ -0,0 +1,20 @@ +# Copyright 2019, Oath Inc. +# Licensed under the terms of the Apache 2.0 license. See LICENSE file in project root for terms. + +from .arguments import parse_arguments + + +args = parse_arguments() +headers_graphql = { + 'Accept': 'application/vnd.github.vixen-preview', + 'Authorization': 'bearer %s' % (args.graphql_authorization), +} + +headers_jira = { + 'Content-Type': 'application/json', + 'Authorization': 'Basic %s' % (args.jira_authorization) +} + +graphql_url = "https://api.github.com/graphql" + +jira_url = parse_arguments().jira_url diff --git a/graph_ql.py b/src/github_security_alerts/graph_ql.py similarity index 65% rename from graph_ql.py rename to src/github_security_alerts/graph_ql.py index b66e2cd..97d85a4 100644 --- a/graph_ql.py +++ b/src/github_security_alerts/graph_ql.py @@ -1,22 +1,21 @@ # Copyright 2019, Oath Inc. # Licensed under the terms of the Apache 2.0 license. See LICENSE file in project root for terms. -import requests -import pprint -from queries import query -from constants import headers_graphql -from constants import graphql_url -from constants import jira_url -from arguments import jira_project_key -from constants import headers_jira -from arguments import vulnerabilities_issue_created_track_path import json import collections -# A simple function to use requests.post to make the API call. Note the json= section. +import requests + +from .arguments import parse_arguments +from .queries import query +from .constants import headers_graphql, graphql_url, jira_url, headers_jira + + +args = parse_arguments() def run_query(query): + """A simple function to use requests.post to make the API call. Note the json= section.""" request = requests.post(graphql_url, json={'query': query}, headers=headers_graphql) if request.status_code == 200: return request.json() @@ -24,7 +23,7 @@ def run_query(query): raise Exception("Query failed to run by returning code of {}. {}".format(request.status_code, query)) -result = run_query(query) # Execute the query +result = run_query(query) # Execute the query def get_vulnerabilities(): @@ -53,25 +52,24 @@ def get_vulnerabilities(): def create_jira_issue(): - for i in range(0,len(vulnerabilities_keys_list)): + for i in range(0, len(vulnerabilities_keys_list)): if vulnerabilities_keys_list[i] not in vulnerabilities_issues_created_keys_list and \ vulnerabilities_values_list[i] not in vulnerabilities_issues_created_values_list and \ - vulnerabilities_keys_list[i] not in open(vulnerabilities_issue_created_track_path).read(): + vulnerabilities_keys_list[i] not in open(args.vulnerabilities_issue_created_track_path).read(): - issue_body = {"fields": { - "project": - { - "key": "%s" % (jira_project_key) + issue_body = { + "fields": { + "project": { + "key": "%s" % (args.jira_project_key) }, - "summary": "Security vulnerability issues found in project %s" % (vulnerabilities_keys_list[i]), - "description": "Following are the list of vulnerabilities found for the above project %s" % - (vulnerabilities_values_list[i]), - "issuetype": { - "name": "Defect" + "summary": "Security vulnerability issues found in project %s" % (vulnerabilities_keys_list[i]), + "description": "Following are the list of vulnerabilities found for the above project %s" % (vulnerabilities_values_list[i]), + "issuetype": { + "name": "Defect" + } } } - } issue_body_data = json.dumps(issue_body) request = requests.post(jira_url, data=issue_body_data, headers=headers_jira) @@ -81,17 +79,16 @@ def create_jira_issue(): tracked_repos = '\n'.join(vulnerabilities_issues_created_keys_list) - f = open(vulnerabilities_issue_created_track_path, "w") + f = open(args.vulnerabilities_issue_created_track_path, "w") f.write(tracked_repos) if request.status_code == 201: print(request.json()) else: - raise Exception("Issue failed to be created by returning code of {}. {}".format(request.status_code, - request.json())) + raise Exception("Issue failed to be created by returning code of {}. {}".format(request.status_code, request.json())) - if len(vulnerabilities_issues_created_keys_list) == len(vulnerabilities_keys_list) and\ + if len(vulnerabilities_issues_created_keys_list) == len(vulnerabilities_keys_list) and \ len(vulnerabilities_issues_created_values_list) == len(vulnerabilities_values_list): return True else: diff --git a/queries.py b/src/github_security_alerts/queries.py similarity index 89% rename from queries.py rename to src/github_security_alerts/queries.py index 9b4e9ac..5bf6b54 100644 --- a/queries.py +++ b/src/github_security_alerts/queries.py @@ -1,5 +1,5 @@ -#Copyright 2019, Oath Inc. -#Licensed under the terms of the Apache 2.0 license. See LICENSE file in root for terms. +# Copyright 2019, Oath Inc. +# Licensed under the terms of the Apache 2.0 license. See LICENSE file in root for terms. query = """{organization (login: "yahoo") diff --git a/tests/test_arguments.py b/tests/test_arguments.py new file mode 100644 index 0000000..cc3622b --- /dev/null +++ b/tests/test_arguments.py @@ -0,0 +1,22 @@ +from unittest import TestCase +import sys +from github_security_alerts.arguments import parse_arguments + + +class ArgumentsTestCase(TestCase): + # Have setup and teardown save and restore the sys.argv value so the + # tests can freely modify it. + def setUp(self): + self._orig_argv = sys.argv + + def tearDown(self): + sys.argv = self._orig_argv + + def test__all_arguments_passed(self): + sys.argv = ['graph_ql.py', 'graph_ql_authorization_key', 'jira_authorization_key', 'jira_url', 'jira_project_key', 'vulnerabilities_issue_created_track_path'] + args = parse_arguments() + print(args) + self.assertEqual(args.graphql_authorization, 'graph_ql_authorization_key') + self.assertEqual(args.jira_authorization, 'jira_authorization_key') + self.assertEqual(args.jira_url, 'jira_url') + self.assertEqual(args.jira_project_key, 'jira_project_key') diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..63d73a5 --- /dev/null +++ b/tox.ini @@ -0,0 +1,12 @@ +[tox] +envlist = py35, py36,py37,py38 +skip_missing_interpreters=true + +[testenv] +passenv = CI TRAVIS CODECOV_TOKEN TRAVIS_* SD_* +commands = + pytest --junitxml={env:SD_ARTIFACTS_DIR:artifacts}/test/pytest_{envname}.xml -o junit_suite_name={envname} --cov=github_security_alerts --cov-report=xml:{env:SD_ARTIFACTS_DIR:artifacts}/test/coverage.xml tests/ +deps = + pytest + pytest-cov +extras = test