-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: init python plugin from michael's POC
- Loading branch information
0 parents
commit a180e50
Showing
17 changed files
with
783 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
/node_modules/ | ||
__pycache__/ | ||
*.pyc |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
{ | ||
"preset": "node-style-guide", | ||
"requireCapitalizedComments": null, | ||
"requireEarlyReturn": true, | ||
"requireSpacesInAnonymousFunctionExpression": { | ||
"beforeOpeningCurlyBrace": true, | ||
"beforeOpeningRoundBrace": true | ||
}, | ||
"disallowSpacesInNamedFunctionExpression": { | ||
"beforeOpeningRoundBrace": true | ||
}, | ||
"excludeFiles": ["node_modules/**"], | ||
"disallowSpacesInFunction": null | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
var path = require('path'); | ||
var subProcess = require('./sub-process'); | ||
|
||
module.exports = { | ||
inspect: inspect, | ||
}; | ||
|
||
function inspect(root, targetFile, args) { | ||
return Promise.all([ | ||
getMetaData(), | ||
getDependencies(root, targetFile, args), | ||
]) | ||
.then(function (result) { | ||
return { | ||
plugin: result[0], | ||
package: result[1], | ||
}; | ||
}); | ||
} | ||
|
||
function getMetaData() { | ||
return subProcess.execute('python', ['--version']) | ||
.then(function(res) { | ||
return { | ||
name: 'snyk-python-plugin', | ||
runtime: res.stdout || res.stderr, // `python --version` sends to stderr | ||
}; | ||
}); | ||
} | ||
|
||
function getDependencies(root, targetFile, args) { | ||
return subProcess.execute( | ||
'python', | ||
buildArgs(root, targetFile, args), | ||
{ cwd: root } | ||
) | ||
.then(function (result) { | ||
return JSON.parse(result.stdout); | ||
}) | ||
.catch(function (stderr) { | ||
if (typeof stderr === 'string' && | ||
stderr.indexOf('Required package missing') !== -1) { | ||
throw new Error('Please run `pip install -r ' + targetFile + '`'); | ||
} else { | ||
throw new Error(stderr); | ||
} | ||
}); | ||
} | ||
|
||
function buildArgs(root, targetFile, extraArgs) { | ||
var args = [path.resolve(__dirname, '../plug/pip_resolve.py')]; | ||
if (targetFile) { args.push(targetFile); } | ||
if (extraArgs) { args.push(extraArgs); } | ||
return args; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
var childProcess = require('child_process'); | ||
|
||
module.exports.execute = function (command, args, options) { | ||
var spawnOptions = { shell: true }; | ||
if (options && options.cwd) { | ||
spawnOptions.cwd = options.cwd; | ||
} | ||
|
||
return new Promise(function (resolve, reject) { | ||
var stdout = ''; | ||
var stderr = ''; | ||
|
||
var proc = childProcess.spawn(command, args, spawnOptions); | ||
proc.stdout.on('data', function (data) { stdout = stdout + data; }); | ||
proc.stderr.on('data', function (data) { stderr = stderr + data; }); | ||
|
||
proc.on('close', function (code) { | ||
if (code !== 0) { | ||
return reject(stdout || stderr); | ||
} | ||
resolve({ stdout: stdout, stderr: stderr }); | ||
}); | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"name": "snyk-python-plugin", | ||
"version": "0.0.0-placeholder", | ||
"description": "Snyk CLI Python plugin", | ||
"main": "lib/index.js", | ||
"scripts": { | ||
"test": "tap `find ./test -name '*.test.js'`", | ||
"lint": "jscs `find ./lib -name '*.js'` -v && jscs `find ./test -name '*.js'` -v" | ||
}, | ||
"author": "snyk.io", | ||
"license": "Apache-2.0", | ||
"devDependencies": { | ||
"jscs": "^3.0.7", | ||
"semantic-release": "^6.3.6", | ||
"sinon": "^2.3.2", | ||
"tap": "^10.3.2" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
from package import Package | ||
from reqPackage import ReqPackage | ||
|
||
|
||
class DistPackage(Package): | ||
"""Wrapper class for pkg_resources.Distribution instances | ||
:param obj: pkg_resources.Distribution to wrap over | ||
:param req: optional ReqPackage object to associate this | ||
DistPackage with. This is useful for displaying the | ||
tree in reverse | ||
""" | ||
|
||
def __init__(self, obj, req=None): | ||
super(DistPackage, self).__init__(obj) | ||
self.version_spec = None | ||
self.req = req | ||
|
||
def render_as_root(self, frozen): | ||
if not frozen: | ||
return '{0}=={1}'.format(self.project_name, self.version) | ||
else: | ||
return self.__class__.frozen_repr(self._obj) | ||
|
||
def render_as_branch(self, frozen): | ||
assert self.req is not None | ||
if not frozen: | ||
parent_ver_spec = self.req.version_spec | ||
parent_str = self.req.project_name | ||
if parent_ver_spec: | ||
parent_str += parent_ver_spec | ||
return ( | ||
'{0}=={1} [requires: {2}]' | ||
).format(self.project_name, self.version, parent_str) | ||
else: | ||
return self.render_as_root(frozen) | ||
|
||
def as_requirement(self): | ||
"""Return a ReqPackage representation of this DistPackage""" | ||
return ReqPackage(self._obj.as_requirement(), dist=self) | ||
|
||
def as_required_by(self, req): | ||
"""Return a DistPackage instance associated to a requirement | ||
This association is necessary for displaying the tree in | ||
reverse. | ||
:param ReqPackage req: the requirement to associate with | ||
:returns: DistPackage instance | ||
""" | ||
return self.__class__(self._obj, req) | ||
|
||
def as_dict(self): | ||
return {'key': self.key, | ||
'package_name': self.project_name, | ||
'installed_version': self.version} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import pip | ||
|
||
|
||
class Package(object): | ||
"""Abstract class for wrappers around objects that pip returns. | ||
This class needs to be subclassed with implementations for | ||
`render_as_root` and `render_as_branch` methods. | ||
""" | ||
|
||
def __init__(self, obj): | ||
self._obj = obj | ||
self.project_name = obj.project_name | ||
self.key = obj.key | ||
|
||
def render_as_root(self, frozen): | ||
return NotImplementedError | ||
|
||
def render_as_branch(self, frozen): | ||
return NotImplementedError | ||
|
||
def render(self, parent=None, frozen=False): | ||
if not parent: | ||
return self.render_as_root(frozen) | ||
else: | ||
return self.render_as_branch(frozen) | ||
|
||
@staticmethod | ||
def frozen_repr(obj): | ||
fr = pip.FrozenRequirement.from_dist(obj, []) | ||
return str(fr).strip() | ||
|
||
def __getattr__(self, key): | ||
return getattr(self._obj, key) | ||
|
||
def __repr__(self): | ||
return '<{0}("{1}")>'.format(self.__class__.__name__, self.key) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import sys | ||
import os | ||
import argparse | ||
import json | ||
import requirements | ||
import pip | ||
import pkg_resources | ||
import utils | ||
|
||
|
||
def create_tree_of_packages_dependencies(dist_tree, packages_names, req_file_path): | ||
"""Create packages dependencies tree | ||
:param dict tree: the package tree | ||
:param set packages_names: set of select packages to be shown in the output. | ||
:param req_file_path: the path to requirements.txt file | ||
:rtype: dict | ||
""" | ||
DEPENDENCIES = 'dependencies' | ||
FROM = 'from' | ||
VERSION = 'version' | ||
NAME = 'name' | ||
VERSION_SEPARATOR = '@' | ||
DIR_VERSION = '0.0.0' | ||
PACKAGE_FORMAT_VERSION = 'packageFormatVersion' | ||
|
||
tree = utils.sorted_tree(dist_tree) | ||
nodes = tree.keys() | ||
key_tree = dict((k.key, v) for k, v in tree.items()) | ||
|
||
def get_children(n): return key_tree.get(n.key, []) | ||
packages_as_dist_obj = [ | ||
p for p in nodes if p.key in packages_names or p.project_name in packages_names] | ||
|
||
def create_children_recursive(root_package, key_tree): | ||
children_packages_as_dist = key_tree[root_package[NAME].lower()] | ||
for child_dist in children_packages_as_dist: | ||
child_package = {NAME: child_dist.project_name, VERSION: child_dist.installed_version, | ||
FROM: root_package[FROM] + | ||
[child_dist.key + VERSION_SEPARATOR + | ||
child_dist.installed_version]} | ||
create_children_recursive(child_package, key_tree) | ||
root_package[DEPENDENCIES] = { | ||
child_dist.project_name: child_package} | ||
return root_package | ||
|
||
def create_dir_as_root(): | ||
dir_as_root = { NAME: os.path.basename(os.path.dirname(os.path.abspath(req_file_path))), VERSION: DIR_VERSION, | ||
FROM: [os.path.basename(os.path.dirname(os.path.abspath(req_file_path)))], DEPENDENCIES: {}, | ||
PACKAGE_FORMAT_VERSION: 'pip:0.0.1'} | ||
return dir_as_root | ||
|
||
def create_package_as_root(package, dir_as_root): | ||
package_as_root = {NAME: package.project_name.lower(), VERSION: package._obj._version, | ||
FROM: ["{}{}{}".format(dir_as_root[NAME], VERSION_SEPARATOR, dir_as_root[VERSION])] + | ||
["{}{}{}".format(package.project_name.lower(), | ||
VERSION_SEPARATOR, package._obj._version)]} | ||
return package_as_root | ||
dir_as_root = create_dir_as_root() | ||
for package in packages_as_dist_obj: | ||
package_as_root = create_package_as_root(package, dir_as_root) | ||
package_tree = create_children_recursive(package_as_root, key_tree) | ||
dir_as_root[DEPENDENCIES][package_as_root[NAME]] = package_tree | ||
return dir_as_root | ||
|
||
|
||
def create_dependencies_tree_by_req_file_path(requirements_file_path): | ||
# get all installed packages | ||
pkgs = pip.get_installed_distributions(local_only=False, skip=[]) | ||
|
||
# get all installed packages's distribution object | ||
dist_index = utils.build_dist_index(pkgs) | ||
|
||
# get all installed distributions tree | ||
dist_tree = utils.construct_tree(dist_index) | ||
|
||
# open the requirements.txt file and create dependencies tree out of it | ||
with open(requirements_file_path, 'r') as requirements_file: | ||
req_list = list(requirements.parse(requirements_file)) | ||
required = [req.name for req in req_list] | ||
installed = [p for p in dist_index] | ||
for r in required: | ||
if r.lower() not in installed: | ||
sys.exit('Required package missing') | ||
package_tree = create_tree_of_packages_dependencies( | ||
dist_tree, [req.name for req in req_list], requirements_file_path) | ||
print(json.dumps(package_tree)) | ||
|
||
|
||
if __name__ == '__main__': | ||
if(not sys.argv[1]): | ||
print('Expecting requirements.txt Path An Argument') | ||
sys.exit(create_dependencies_tree_by_req_file_path(sys.argv[1])) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import pkg_resources | ||
import utils | ||
from package import Package | ||
|
||
|
||
class ReqPackage(Package): | ||
"""Wrapper class for Requirements instance | ||
:param obj: The `Requirements` instance to wrap over | ||
:param dist: optional `pkg_resources.Distribution` instance for | ||
this requirement | ||
""" | ||
|
||
UNKNOWN_VERSION = '?' | ||
|
||
def __init__(self, obj, dist=None): | ||
super(ReqPackage, self).__init__(obj) | ||
self.dist = dist | ||
|
||
@property | ||
def version_spec(self): | ||
specs = self._obj.specs | ||
return ','.join([''.join(sp) for sp in specs]) if specs else None | ||
|
||
@property | ||
def installed_version(self): | ||
if not self.dist: | ||
return utils.guess_version(self.key, self.UNKNOWN_VERSION) | ||
return self.dist.version | ||
|
||
def is_conflicting(self): | ||
"""If installed version conflicts with required version""" | ||
# unknown installed version is also considered conflicting | ||
if self.installed_version == self.UNKNOWN_VERSION: | ||
return True | ||
ver_spec = (self.version_spec if self.version_spec else '') | ||
req_version_str = '{0}{1}'.format(self.project_name, ver_spec) | ||
req_obj = pkg_resources.Requirement.parse(req_version_str) | ||
return self.installed_version not in req_obj | ||
|
||
def render_as_root(self, frozen): | ||
if not frozen: | ||
return '{0}=={1}'.format(self.project_name, self.installed_version) | ||
elif self.dist: | ||
return self.__class__.frozen_repr(self.dist._obj) | ||
else: | ||
return self.project_name | ||
|
||
def render_as_branch(self, frozen): | ||
if not frozen: | ||
req_ver = self.version_spec if self.version_spec else 'Any' | ||
return ( | ||
'{0} [required: {1}, installed: {2}]' | ||
).format(self.project_name, req_ver, self.installed_version) | ||
else: | ||
return self.render_as_root(frozen) | ||
|
||
def as_dict(self): | ||
return {'key': self.key, | ||
'package_name': self.project_name, | ||
'installed_version': self.installed_version, | ||
'required_version': self.version_spec} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
from .parser import parse # noqa | ||
|
||
_MAJOR = 0 | ||
_MINOR = 1 | ||
_PATCH = 0 | ||
|
||
|
||
def version_tuple(): | ||
''' | ||
Returns a 3-tuple of ints that represent the version | ||
''' | ||
return (_MAJOR, _MINOR, _PATCH) | ||
|
||
|
||
def version(): | ||
''' | ||
Returns a string representation of the version | ||
''' | ||
return '%d.%d.%d' % (version_tuple()) | ||
|
||
|
||
__version__ = version() |
Oops, something went wrong.