Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Linted and updated to python 3.x #6

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[flake8]
ignore = D40
max-line-length = 160
exclude = tests/*
max-complexity = 10
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
language: python
python:
- "2.7"
- "3.6"
services:
- docker
# command to install dependencies
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
FROM python:2
FROM python:3.6

RUN git clone https://github.com/feedhenry/jenkins-plugin-install.git
RUN git clone https://github.com/adamsaleh/jenkins-plugin-install.git
WORKDIR jenkins-plugin-install
RUN pip install -r requirements.txt && python setup.py install
4 changes: 1 addition & 3 deletions jpinstall/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
"""
Jenkins Plugin Install module
"""
"""Jenkins Plugin Install module"""
import jpinstall.deps
import jpinstall.download
import jpinstall.jenkins
Expand Down
41 changes: 19 additions & 22 deletions jpinstall/cli.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Jenkins Package Install
"""Main method of Jenkins Package Install.

Usage:
jpi install [--conf conf] [--tmp tmpdir] [--dry-run] [--restart] <file_with_packages>
Expand All @@ -12,36 +12,33 @@
--dry-run Don't install any packages on jenkins
"""

import ConfigParser
from configparser import ConfigParser

import requests
from docopt import docopt

from deps import deduplicate_downloads, plugin_str_to_data, installable_downloads
from download import download_all
from jenkins import JenkinsPlugins
from jpinstall.deps import deduplicate_downloads, plugin_str_to_data
from jpinstall.download import download_all
from jpinstall.jenkins import JenkinsPlugins


def list_plugins(plugins):
"""
Utility function to pretty print the list of plugins
"""
"""Utility function to pretty print the list of plugins."""
for plugin in plugins:
for version in plugins[plugin]:
print plugin+":"+version
print(plugin + ":" + version)
return


def get_plugins_to_install(path):
"""Utility function to loads the file with list of plugins to install."""
plugin_str = ""
with open(path, 'r') as plugins_file:
plugin_str = str(plugins_file.read())
return plugin_str_to_data(plugin_str)


def install_plugins(jenkins, opts):
"""
Implementation of the install sub-command
"""
"""Implementation of the install sub-command."""
plugins = jenkins.plugins()
path = opts['<file_with_packages>']
plugins_to_install = get_plugins_to_install(path)
Expand All @@ -53,22 +50,21 @@ def install_plugins(jenkins, opts):
downloaded = deduplicate_downloads(plugins, downloaded)

if len(downloaded) > 0:
print "Plugins to be installed:"
print("Plugins to be installed:")
for plugin, version, path in downloaded:
print plugin + ":" + version
print(plugin + ":" + version)

if opts['--dry-run']:
print "Dry run, not uploading packages."
print("Dry run, not uploading packages.")
else:
print "Uploading plugins to " + jenkins.url
print("Uploading plugins to " + jenkins.url)
jenkins.install_plugins(plugins, downloaded)
else:
print "No new plugins will be installed"
print("No new plugins will be installed")


def main():
"""
The main function stand-in
"""
"""The main function that calls the sub-commands."""
opts = docopt(__doc__)
config = ConfigParser.ConfigParser()
config.read([opts['--conf']])
Expand All @@ -89,8 +85,9 @@ def main():
return
if opts['version']:
response = jenkins.version()
print response
print(response)
return


if __name__ == "__main__":
main()
102 changes: 53 additions & 49 deletions jpinstall/deps.py
Original file line number Diff line number Diff line change
@@ -1,129 +1,135 @@
"""
Helper functions related to resolving *.hpi dependencies
"""
"""Helper functions related to resolving *.hpi dependencies."""
import sys
import re
from zipfile import ZipFile
import copy


def remove_dep_metadata(depstr):
"""
Plugin dependencies can have metadata we don't care about,
i.e both "a:1.3" and "a:1.3;some-data" should result in ["a","1.3"]
Removes metadata we don't care about from dependencies.

i.e both "package:1.3" and "package:1.3;some-data" should result in ["package","1.3"]
"""
if ";" in depstr:
return depstr[:depstr.rfind(";")].strip().split(":")
return depstr.strip().split(":")


def assume_min_or_latest(dep):
"""
Normalizes the list of dependencies to have only major version

Assumes a list of versions where version is a list containing at least one .
If no version number is present, assume "latest"
"""
if len(dep) == 1:
return [dep[0], "latest"]
else:
return dep[0:2]


def plugin_str_to_data(plugin_str):
"""
Parses a newline delimited string to
list of [[plugin_name,plugin_version]]
"""
"""Parses a newline delimited string to list of `[[plugin_name,plugin_version]]`."""
return [x.strip().split(":")
for x in plugin_str.strip().split("\n")
if x != ""]


def dep_str_to_data(plugin_str):
"""
Parses the depstring in manifest.xml to
list of [[plugin_name,plugin_version]]
Parses the depstring in manifest.xml to list of [[plugin_name,plugin_version]]

It filters out optional deps, and removes other metadata.
"""
return [assume_min_or_latest(remove_dep_metadata(x))
for x in plugin_str.split(",")
if x != ""]
# TODO: if optional deps are already installed, you need to check the version
# and not "resolution:=optional" in x]


def str_to_ver(version):
"""
Converts semver to list of ints
"""Converts semver to list of ints

i.e. "1.2.3-0" to [1,2,3,0]
"latest" is represented as maxint
"""
if version == "latest":
return sys.maxint
return [int(p) for p in re.split(r"\.|-", version)]
return sys.maxsize
return [int(p) for p in re.split(r"\.|-", version)]


def ver_to_str(version):
"""
Converts list of ints to string

i.e. [1,2,3,0] to "1.2.3.0"
"""
if version == sys.maxint:
if version == sys.maxsize:
return "latest"
return ".".join(str(x) for x in version)


def add_plugin(plugins, name, version, deps):
"""
Utility function for adding to map of maps
"""
if not name in plugins:
"""Utility function for adding to map of maps."""
if name not in plugins:
plugins[name] = {}
plugins[name][version] = deps
return plugins


def get_latest_version_present(plugins, plugin):
"""
From all of the stated versions of the plugin, get the newest.
"""
"""From all of the stated versions of the plugin, get the newest."""
return max(str_to_ver(v) for v in plugins[plugin].keys())


def is_greater_version_present(plugins, plugin, version):
"""
For i.e. comparing plugins version to the versions of the plugins already downloaded.
Returns True, if there already is greater version.
Compares plugins version to the versions of the plugins already downloaded.

Returns True, if there already is greater version in the `plugins` list.
"""
if not plugin in plugins:
if plugin not in plugins:
return False
latest = get_latest_version_present(plugins, plugin)
current = str_to_ver(version)
return current <= latest


def normalize_manifest(manifest_bin):
"""
"""Normalizes manifest.

Manifest file has crlf line ends, as well as sometimes incorrectly split lines.
These can be identified as lines that start with a space.
We first remove the bad splits, and then replace crlf with | as a separator.
"""
normalized = manifest_bin \
.replace(b"\r\n ", b"") \
.replace(b"\r\n", b"|")
correct_lines = manifest_bin.replace(b"\r\n ", b"")
normalized = correct_lines.replace(b"\r\n", b"|")
return normalized.decode('utf-8')

return str(normalized)

def parse_manifest(manifest_str):
"""
Parse the normalized manifest_str to a dictionary
"""
"""Parse the normalized manifest_str to a dictionary"""
return {
k:v
k: v
for k, v
in [l.strip().split(":", 1)
for l
in manifest_str.split("|") if ":" in l]}


def deduplicate_downloads(plugins, downloaded):
"""
Function to remove references to downloaded plugins,
where there is already a newer version present.
"""
"""Removes references to downloaded plugins, where there is already a newer version present."""
return list(reversed([
[plugin, version, path]
for plugin, version, path in downloaded
if get_latest_version_present(plugins, plugin) == str_to_ver(version)]))

def installable_downloads(installed,plugins,downloaded):

def installable_downloads(installed, plugins, downloaded):
"""Filters the downloaded plugins to ones actually installable.

The plugin is installable if all of its dependencies (found in the `plugins` param)
are satisfied by plugins in the `installed` list.
"""
installable = []
remaining = []
for plugin, version, path in downloaded:
Expand All @@ -135,18 +141,16 @@ def installable_downloads(installed,plugins,downloaded):
remaining.append([plugin, version, path])
return installable, remaining


def get_manifest_for_hpi(path):
"""
Extracts and returns manifest from hpi file
"""
"""Extracts and returns manifest from hpi file."""
with ZipFile(path) as hpi:
with hpi.open('META-INF/MANIFEST.MF', "r") as manifest:
return manifest.read()


def get_dependencies_for_hpi(path):
"""
Extracts dependencies for a hpi file from its manifest
"""
"""Extracts dependencies for a hpi file from its manifest."""
manifest_bin = get_manifest_for_hpi(path)
manifest_str = normalize_manifest(manifest_bin)
manifest_data = parse_manifest(manifest_str)
Expand Down
26 changes: 10 additions & 16 deletions jpinstall/download.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,34 @@
"""
Utility functions for downloading jenkins packages
"""
"""Utility functions for downloading jenkins packages."""
import os
import shutil
import requests
import deps
import jpinstall.deps as deps


def download_file(url, path):
"""
Simple wrapper for downloading files with requests
"""
"""Simple wrapper for downloading files with requests."""
response = requests.get(url, stream=True)
with open(path, 'wb') as opened:
shutil.copyfileobj(response.raw, opened)
return path


def download_plugin(name, version, folder='.'):
"""
Downloads a plugin from https://updates.jenkins-ci.org/ based on
shortname and version
"""
"""Downloads a plugin from https://updates.jenkins-ci.org/ based on shortname and version."""
if os.path.isdir(folder):
url = "https://updates.jenkins-ci.org/"
url += "download/plugins/{0}/{1}/{0}.hpi".format(name, version)
path = "{0}/{1}_{2}.hpi".format(folder, name, version)
print "Saving {0} to {1}".format(url, path)
print("Saving {0} to {1}".format(url, path))
return download_file(url, path)


def download_all(plugin_data, folder, plugins, downloaded):
"""
Downloads plugins and their dependencies recursively.
"""
"""Downloads plugins and their dependencies recursively."""
for name, version in [d for d in plugin_data if ":".join(d) not in plugins]:
if deps.is_greater_version_present(plugins, name, version):
ver = deps.ver_to_str(deps.get_latest_version_present(plugins, name))
print name + " already has version "+ ver + " present"
print(name + " already has version " + ver + " present")
else:
path = download_plugin(name, version, folder)
downloaded.append([name, version, path])
Expand Down
Loading