#!/usr/bin/env python
# -*- coding: utf-8 -*
# Copyright: [CUP] - See LICENSE for details.
# Authors: liushuxian(liushuxian)
"""
This module provides Artifacts and FTPArtifacts object.
"""
import ftplib
import os
import pprint
import socket
import urlparse
from cup.jenkinslib.internal import exception
class ArtifactsBase(object):
"""Base of artifacts."""
def __init__(self, build, name="", path="", is_dir=True):
"""initialize Artifacts object."""
self.children = None
self.build = build
self.name = name
self.path = path
self.is_dir = is_dir
def get_jenkins_obj(self):
"""get object of current jenkins."""
return self.build.job.jenkins
def poll(self):
"""poll out artifacts info."""
if self.children is None and self.is_dir:
self.children = self._poll()
return self.children
def _poll(self):
"""poll out artifacts info."""
raise exception.NotImplementedMethod("_poll")
def download(self, path="./"):
"""download these artifacts.
Args:
path: local path to store artifacts, if not exists, create it.
"""
if not os.path.isdir(path):
try:
os.makedirs(path)
except OSError as err:
if not os.path.isdir(path):
raise exception.OSIOError(err)
self._download(path)
def _download(self, path="./"):
"""do download."""
raise exception.NotImplementedMethod("_download")
def get_child_artifacts(self, child):
"""get child artifacts by filename or dirname."""
if not self.is_dir:
raise exception.NoArtifacts(child)
self.poll()
try:
return self.children[child]
except KeyError:
items = child.split('/', 1)
if len(items) > 1 and items[0] in self.children:
return self.children[items[0]][items[1]]
raise exception.NoArtifacts(child)
def __getitem__(self, child):
"""get child artifacts by filename or dirname."""
return self.get_child_artifacts(child)
def pprint(self):
"""print all the child of this object."""
return pprint.pprint(self.children)
def __str__(self):
return self.path
@property
def url(self):
"""get artifact url."""
raise exception.NotImplementedMethod("url")
class Artifacts(ArtifactsBase):
"""Represents artifacts, file or directory."""
pass
class FTPArtifacts(ArtifactsBase):
"""Represents artifacts on FTP.
Because ftp connection is used, close should be called if connected.
It is recommended to use with as like:
with FTPArtifacts(build) as af:
bin_af = af["bin"]
bin_af.download("./output")
"""
def __init__(self, build, name="", path="", is_dir=True):
"""initialize FTPArtifacts object."""
self.ftp = None
super(FTPArtifacts, self).__init__(build, name, path, is_dir)
def connect(self):
"""connect to ftp, return ftp connection."""
if self.ftp is None:
jenkins = self.get_jenkins_obj()
if jenkins.ftp_host is None:
raise exception.FtpError("cannot connect to ftp server before enable ftp")
self.ftp = ftplib.FTP()
try:
self.ftp.connect(jenkins.ftp_host, jenkins.ftp_port)
self.ftp.login(jenkins.ftp_username, jenkins.ftp_password)
except ftplib.error_perm as err:
self.ftp.close()
self.ftp = None
raise exception.FtpError(err)
except socket.error as err:
self.ftp.close()
self.ftp = None
raise exception.FtpError(err)
return self.ftp
def __enter__(self):
"""connect to ftp, return self."""
self.connect()
return self
def close(self):
"""close ftp connection."""
if self.ftp:
self.ftp.close()
self.ftp = None
def __exit__(self, type, value, traceback):
"""close ftp connection."""
self.close()
return False
def _poll(self, tree=None):
"""poll out artifacts info."""
def do_poll(ftp_conn):
"""do poll with connected ftp connection."""
child_list = []
ftp_conn.dir(self.path, child_list.append)
children = {}
for child in child_list:
name = child.split()[-1]
sub_path = os.path.join(self.path, name)
is_dir = child.startswith("d")
children[name] = self.__class__(self.build, name, sub_path, is_dir)
return children
if self.ftp:
return do_poll(self.ftp)
with self:
return do_poll(self.ftp)
def _download(self, path="./", ftp=None):
"""do download."""
def do_download(ftp_conn):
"""do download with connected ftp connection."""
save_path = os.path.join(path, self.name)
if self.is_dir:
if not os.path.isdir(save_path):
os.makedirs(save_path)
children = self.poll()
for child in children:
with children[child] as af:
# download child with same ftp connection
af._download(save_path, ftp)
else:
try:
with open(save_path, "w+") as fd:
ftp_conn.retrbinary(u"RETR %s" % self.path, fd.write)
except OSError as err:
raise exception.FtpError(err)
except IOError as err:
raise exception.FtpError(err)
if ftp:
do_download(ftp)
elif self.ftp:
do_download(self.ftp)
else:
with self:
do_download(self.ftp)
@property
def url(self):
"""get ftp artifact url."""
jenkins = self.get_jenkins_obj()
if jenkins.ftp_host is None:
raise exception.FtpError("cannot get ftp url before enable ftp")
netloc = []
if jenkins.ftp_username:
netloc.append(jenkins.ftp_username)
if jenkins.ftp_password:
netloc.append(":")
netloc.append(jenkins.ftp_password)
netloc.append("@")
netloc.append(jenkins.ftp_host)
if jenkins.ftp_port:
netloc.append(":")
netloc.append(str(jenkins.ftp_port))
netloc = "".join(netloc)
return urlparse.urlunparse(("ftp", netloc, self.path, "", "", ""))