Initial commit (version 1.0)

This commit is contained in:
Anton Kirilenko 2012-09-18 13:26:44 +04:00
commit 960ab0feea
10 changed files with 670 additions and 0 deletions

35
Makefile Normal file
View file

@ -0,0 +1,35 @@
#############################################################################
# File : Makefile
# Package : abf_console_client
# Author : Anton Kirilenko <anton.kirilenko@rosalab.ru>
#############################################################################
PYTHON=python
PYVER := $(shell $(PYTHON) -c 'import sys; print "%.3s" %(sys.version)')
PYSYSDIR := $(shell $(PYTHON) -c 'import sys; print sys.prefix')
PYLIBDIR = $(PYSYSDIR)/lib/python$(PYVER)
PKGDIR = $(PYLIBDIR)/site-packages
BINDIR=/usr/bin
ETCDIR=/etc
MANDIR=/usr/share/man
FILES = abf/console/*.py
all:
@echo "Nothing to do. Run 'make install' or 'make clean'"
clean:
rm -f *~ *.pyc *.pyo
install:
mkdir -p $(DESTDIR)$(PKGDIR) $(DESTDIR)$(BINDIR) $(DESTDIR)$(MANDIR)/man1
cp -p --parents $(FILES) $(DESTDIR)$(PKGDIR)
cp -p "abf.py" $(DESTDIR)$(BINDIR)"/abf"

29
abf-console-client.spec Normal file
View file

@ -0,0 +1,29 @@
Name: abf-console-client
Version: 1.0
Release: 0
Summary: Python API to ABF (https://abf.rosalinux.ru)
Group: System/Configuration/Packaging
License: GPLv2
URL: http://wiki.rosalab.ru/en/index.php/ABF_Console_Client
Source0: %{name}-%{version}.tar.gz
BuildArch: noarch
Requires: python-abf
%description
Console client for ABF (https://abf.rosalinux.ru).
%prep
%setup -q -n %{name}
%install
rm -rf %{buildroot}
make install DESTDIR=$RPM_BUILD_ROOT
%files
%defattr(-,root,root,-)
%dir %{py_puresitedir}/abf/console
%{py_puresitedir}/abf/console/*.py*
%{_bindir}/abf

279
abf.py Executable file
View file

@ -0,0 +1,279 @@
#!/usr/bin/python -tt
# -*- coding: UTF-8 -*-
import sys
import argparse
import os
from abf.console.config import Config
from abf.console.log import Log
cfg = Config()
log = Log('abf')
from abf.console.misc import *
from abf.model import Models
domain = cfg['main']['domain']
login = cfg['user']['login']
password = cfg['user']['password']
#models = Models(domain, login, password)
#res = models.platforms['15']
#res = models.platforms['17']
#res = models.repositories[('18', '24')]
#print res
#exit()
def parse_command_line():
global command_line
parser = argparse.ArgumentParser(description='ABF Console CLient')
parser.add_argument('-v', '--verbose', action='store_true', help='be verbose')
subparsers = parser.add_subparsers(help='sub-command help')
# get
parser_get = subparsers.add_parser('get', help='get help')
parser_get.add_argument('project', action='store', help='project name. Format: "project_name" or "group/project_name"')
parser_get.add_argument('-b', '--branch', action='store', help='branch to checkout')
parser_get.set_defaults(func=get)
# put
parser_get = subparsers.add_parser('put', help='commit changes (with -am "message") and push')
parser_get.add_argument('message', action='store', help='A message to commit with')
parser_get.set_defaults(func=put)
# build
parser_build = subparsers.add_parser('build', help='build help')
parser_build.add_argument('project', action='store', nargs='?', help='project name')
parser_build.add_argument('-b', '--branch', action='store', help='Branch to build')
parser_build.add_argument('-t', '--tag', action='store', help='Tag to build')
parser_build.add_argument('-p', '--target-platform', action='store', help='Platform to build into')
parser_build.add_argument('-a', '--arches', action='append', nargs='+', help='Architectures to build, '
'can be set more than onece. If not set - use all the available architectures')
parser_build.add_argument('-r', '--repository', action='append', nargs='+', help='Repositories to biuld with (platform/repository)')
parser_build.set_defaults(func=build)
# buildstatus
parser_build = subparsers.add_parser('buildstatus', help='Get a building task status')
parser_build.add_argument('ID', action='store', nargs='?', help='Build list ID')
parser_build.add_argument('-l', '--logs', action='store_true', help='Also download logs')
parser_build.set_defaults(func=buildstatus)
command_line = parser.parse_args(sys.argv[1:])
def get():
log.debug('GET started')
proj = command_line.project
tmp = proj.split('/')
if len(tmp) > 2:
log.error('Specify a project name as "group_name/project_name" or just "project_name"')
exit(1)
if len(tmp) == 1:
proj = '%s/%s' % (cfg['user']['default_group'], proj)
uri = "%s/%s.git" % (cfg['user']['git_uri'], proj)
cmd = ['git', 'clone', uri]
if command_line.branch:
cmd += ['-b', command_line.branch]
#log.debug('Executing command ' + str(cmd))
execute_command(cmd, log, print_to_stdout=True, exit_on_error=True)
#os.execlp(*cmd)
def put():
log.debug('PUT started')
cmd = ['git', 'commit', '-a', '-m', command_line.message]
execute_command(cmd, log, print_to_stdout=True, exit_on_error=True)
log.info('Commited.')
cmd = ['git', 'push']
execute_command(cmd, log, print_to_stdout=True, exit_on_error=True)
log.info('Pushed')
def build():
log.debug('BUILD started')
IDs = {
'arches':[],
'version':None,
'target_platform':None,
'repositories':[],
'platforms': [],
}
if command_line.project:
tmp = command_line.project.split('/')
if len(tmp) != 2:
log.error('The project format is "owner_name/project_name"')
exit(1)
owner_name = tmp[0]
project_name = tmp[1]
else:
owner_name, project_name = get_project_name()
if not project_name:
log.error('You are not in git repository. Specify the project name!')
exit(1)
if command_line.branch and command_line.tag:
log.error('Branch and tag were specified simultaneously!')
exit(1)
log.debug('Project name: %s/%s' % (owner_name, project_name))
models = Models(domain, login, password)
nbp = models.newbuildpages[(owner_name, project_name)]
if command_line.arches:
arches = []
for item in command_line.arches:
arches += item
arches = list(set(arches))
for arch in arches:
if arch not in nbp.arches.keys():
log.error("Error: can not build for arch %s" % arch)
exit(1)
else:
arches = nbp.arches.keys()
for arch in arches:
IDs['arches'].append(nbp.arches[arch]['value'])
log.debug('Architectures: %s' % arches)
# try to resolve a version from branch or tag
version = None
if command_line.branch:
if command_line.branch in nbp.versions['branches']:
version = command_line.branch
if 'latest_' + command_line.branch in nbp.versions['branches']:
version = 'latest_' + command_line.branch
if command_line.tag:
if command_line.tag in nbp.versions['tags']:
version = command_line.tag
if (command_line.branch or command_line.tag) and not version:
if command_line.branch:
log.error('Selected branch (%s) is not correct' % command_line.branch)
if command_line.tag:
log.error('Selected tag (%s) is not correct' % command_line.tag)
exit(1)
# try to resolve a version fromplatform
if (not command_line.branch and not command_line.tag):
if not command_line.target_platform:
log.error('You have to specify either platform or version (tag or branch), or both of them')
exit(1)
else:
plat = command_line.target_platform.split('/')[0]
if ('latest_' + plat) in nbp.versions['branches']:
version = 'latest_' + plat
else:
log.error("Could not resolve version from platform name")
exit(1)
log.debug('Version: %s' % version)
# If version is selected via command line and correct,
# but platform is not selected
platform = None
if version and not command_line.target_platform:
tmp = version
if tmp.startswith('latest_'):
tmp = tmp[7:]
if tmp + '/main' in nbp.target_platforms:
platform = tmp + '/main'
if command_line.target_platform:
tmp = command_line.target_platform.split('/')
if len(tmp) == 1:
tmp.append('main')
elif len(tmp) > 2:
log.error("Target platform format is 'platform_name' or 'platform_name/project_name'")
exit(1)
p = tmp[0] + '/' + tmp[1]
if p in nbp.target_platforms:
platform = p
else:
log.error("Target platform specified (%s) is not correct!" % (command_line.target_platform))
exit(1)
log.debug('Platform: %s' % platform)
# try to resolve platform
if version and not platform:
log.error('Could not resolve platform. Please, specify it.')
exit(1)
if not version and platform:
log.error('Could not resolve version (branch or tag). Please, specify it.')
exit(1)
#resolve target platform
IDs['target_platform'] = nbp.target_platforms[platform]['value']
if version in nbp.versions['branches']:
IDs['version'] = nbp.versions['branches'][version]['value']
if version in nbp.versions['tags']:
IDs['version'] = nbp.versions['tags'][version]['value']
repos = []
# try to resolve a repository
plat = platform.split('/')[0]
repo = platform.split('/')[1]
if plat in nbp.platforms and repo in nbp.platforms[plat]['repositories']:
repos = [(plat, repo)]
if command_line.repository:
repos = []
tmp = []
for item in command_line.repository:
tmp += item
for repo in tmp:
if len(repo.split('/')) != 2:
log.error('Repository format: platform/repository. "%s" is not correct' % repo)
exit(2)
p = repo.split('/')[0]
r = repo.split('/')[1]
repos.append((p, r))
if not p in nbp.platforms:
log.error('Platform specified (%s) is not correct!' % p)
exit(2)
if not r in nbp.platforms[p]['repositories']:
log.error('Repository specified (%s/%s) is not correct!' % (p, r) )
exit(2)
if not repos:
log.error('Repository to build with could not be resolved. Please, specify it.')
exit(2)
for plat, repo in repos:
IDs['repositories'].append(nbp.platforms[plat]['repositories'][repo]['value'])
IDs['platforms'].append(nbp.platforms[plat]['value'])
IDs['platforms'] = list(set(IDs['platforms']))
nbp.build(IDs)
def buildstatus():
log.debug('BUILDSTATUS started')
if not command_line.ID:
log.error("Enter the ID, please. It can not be resolved automatically now (not implemented).")
exit(1)
models = Models(domain, login, password)
bl = models.buildlists[command_line.ID]
print '%-20s%s' %('Owner:', bl.owner_name)
print '%-20s%s' %('Status:', bl.status)
print '%-20s%s' %('User:', bl.user_name)
print '%-20s%s' %('Build for platform:', bl.platform)
print '%-20s%s' %('Repository:', bl.repository)
print '%-20s%s' %('Architecture:', bl.arch)
print '%-20s%s' %('Notified at:', bl.notified_at)
print '%-20s%s' %('Packages:', ', '.join(bl.packages))
if __name__ == '__main__':
parse_command_line()
if command_line.verbose:
Log.set_verbose()
command_line.func()

0
abf/__init__.py Normal file
View file

1
abf/api Symbolic link
View file

@ -0,0 +1 @@
/home/flid/git/abf/akirilenko/python-abf/abf/api/

0
abf/console/__init__.py Normal file
View file

172
abf/console/config.py Normal file
View file

@ -0,0 +1,172 @@
import ConfigParser
import os
from abf.console.misc import mkdirs, ask_user
#####################################################
# USAGE:
#
# from abf.console.config import Config
#
# cfg = Config()
# cfg['aaa']['bbb'] = 'ccc'
# print cfg['aaa']['bbb']
# print cfg['aaa'].pop('bbb')
# print cfg.pop('aaa')
#####################################################
class Section(dict):
def __init__(self, config, conf_path, section):
self.section = section
self.config = config
self.conf_path = conf_path
if not section in self.config.sections():
self.config.add_section(self.section)
self.save()
def save(self):
with open(self.conf_path, 'wb') as configfile:
self.config.write(configfile)
def __setitem__(self, key, value):
'''NOTE: value is ignored'''
if key in self and self[key] == value:
return
super(Section, self).__setitem__(key, value)
self.config.set(self.section, key, value)
self.save()
def __getitem__(self, key):
if super(Section, self).__contains__(key):
return super(Section, self).__getitem__(key)
res = self.config.get(section, opt)
def pop(self, key, init=None):
if init is not None and key not in self:
return init
res = super(Section, self).pop(key, init)
self.config.remove_option(self.section, key)
self.save()
return res
class Config(dict):
default_url = 'https://abf.rosalinux.ru'
default_log_path = '/var/log/abf.log'
def __init__(self, conf_path='~/.abfcfg'):
self.conf_path = os.path.expanduser(conf_path)
init = False
if not os.path.isfile(self.conf_path):
mkdirs(os.path.dirname(self.conf_path))
init = True
self.config = ConfigParser.RawConfigParser()
self.config.read(self.conf_path)
sections = self.config.sections()
for section in sections:
self[section] = []
opts = self.config.options(section)
for opt in opts:
self[section][opt] = self.config.get(section, opt)
if init:
self.first_start()
def __setitem__(self, key, value):
'''NOTE: value is ignored'''
if super(Config, self).__contains__(key):
return
super(Config, self).__setitem__(key, Section(self.config, self.conf_path, key))
def __getitem__(self, key):
if not super(Config, self).__contains__(key):
self[key] = []
return super(Config, self).__getitem__(key)
def pop(self, section, init=None):
if init is not None and section not in self:
return init
res = super(Config, self).pop(section, init)
self.config.remove_section(section)
res.save()
return res
def first_start(self):
print("First start")
#self['main'] = []
#self['user'] = []
done = False
while not done:
domain = ask_user('ABF domain [%s]:' % Config.default_url, can_be_empty=True)
domain = domain or Config.default_url
if domain and domain.endswith('/'):
domain = domain[:-1] # remove trailing '/'
parts = domain.split('//')
if len(parts) == 1:
print 'No protocol part specified (http://, https://, etc.)'
continue
if len(parts) > 2:
print 'Double slashe must present only once (in a ptocol part)'
continue
done = True
self['main']['domain'] = domain
user = ask_user('User:', can_be_empty=False)
self['user']['login'] = user
password = ask_user('Password:', can_be_empty=False)
self['user']['password'] = password
git_uri = "%(protocol)s//%(user)s@%(domain)s" % \
dict(protocol=parts[0], user=user, domain=parts[1])
self['user']['git_uri'] = git_uri
res = ask_user('Default group [%s]:' % user, can_be_empty=True)
self['user']['default_group'] = res or user
#configure logging
self['formatters']['keys'] = 'verbose,simple'
self['formatter_verbose']['format'] = '%(asctime)s %(levelname)-7s in %(filename)s:%(funcName)s:%(lineno)d: %(message)s'
self['formatter_simple']['format'] = '%(message)s'
self['loggers']['keys'] = 'root,abf,beaker'
self['logger_root']['handlers'] = 'verbose'
self['logger_root']['propagate'] = '1'
self['logger_root']['level'] = 'DEBUG'
self['logger_root']['qualname'] = ''
self['logger_abf']['handlers'] = 'main'
self['logger_abf']['propagate'] = '0'
self['logger_abf']['level'] = 'DEBUG'
self['logger_abf']['qualname'] = 'abf'
self['logger_models']['handlers'] = 'main'
self['logger_models']['propagate'] = '0'
self['logger_models']['level'] = 'DEBUG'
self['logger_models']['qualname'] = 'models'
self['logger_beaker']['handlers'] = 'verbose'
self['logger_beaker']['propagate'] = '1'
self['logger_beaker']['level'] = 'ERROR'
self['logger_beaker']['qualname'] = 'beaker'
self['handlers']['keys'] = 'verbose,main'
self['handler_verbose']['level'] = 'DEBUG'
self['handler_verbose']['class'] = 'StreamHandler'
self['handler_verbose']['formatter'] = 'verbose'
self['handler_verbose']['args'] = '()'
self['handler_main']['level'] = 'WARNING'
self['handler_main']['class'] = 'StreamHandler'
self['handler_main']['formatter'] = 'simple'
self['handler_main']['args'] = '()'
print('Initial configuration have been completed')
exit()

17
abf/console/log.py Normal file
View file

@ -0,0 +1,17 @@
import os
import logging
import logging.config
class Log:
''' Read the configuration file and create the logging object.'''
@staticmethod
def set_verbose():
logging.getLogger("abf").propagate = 1
logging.getLogger("models").propagate = 1
def __init__(self, name=''):
logging.config.fileConfig(os.path.expanduser('~/.abfcfg'))
self.log = logging.getLogger(name)
def __getattr__(self, attr):
return getattr(self.log, attr)

136
abf/console/misc.py Normal file
View file

@ -0,0 +1,136 @@
import os
import sys
import time
import select
import subprocess
import fcntl
def mkdirs(path):
''' the equivalent of mkdir -p path'''
if os.path.exists(path):
return
path = os.path.normpath(path)
items = path.split('/')
p = ''
for item in items:
p += '/' + item
if not os.path.isdir(p):
os.mkdir(p)
def ask_user(prompt, can_be_empty=False, variants=None):
while True:
sys.stdout.write(prompt)
sys.stdout.flush()
res = sys.stdin.readline()
res = res.strip()
if not can_be_empty and not res:
continue
if variants:
if res in variants:
break
else:
continue
break
return res
class CommandTimeoutExpired(Exception):
pass
class ReturnCodeNotZero(Exception):
def __init__(self, message, code):
super(ReturnCodeNotZero, self).__init__(message)
self.code = code
def get_project_name():
try:
output = execute_command(['git', 'remote', 'show', 'origin', '-n'])
for line in output.split('\n'):
if line.startswith(' Fetch URL:'):
project_name = line.split('/')[-1][:-4]
owner_name = line.split('/')[-2]
return (owner_name, project_name)
return None
except ReturnCodeNotZero:
return None
def execute_command(command, log=None, shell=False, cwd=None, timeout=0, raiseExc=True, print_to_stdout=False, exit_on_error=False):
output = ""
start = time.time()
try:
child = None
if log:
log.debug("Executing command: %s" % command)
child = subprocess.Popen(
command,
shell=shell,
bufsize=0, close_fds=True,
stdin=open("/dev/null", "r"),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
# use select() to poll for output so we dont block
output = logOutput([child.stdout, child.stderr],
start, timeout, print_to_stdout=print_to_stdout)
except:
# kill children if they arent done
if child is not None and child.returncode is None:
os.killpg(child.pid, 9)
try:
if child is not None:
os.waitpid(child.pid, 0)
except:
pass
raise
# wait until child is done, kill it if it passes timeout
niceExit=1
while child.poll() is None:
if (time.time() - start)>timeout and timeout!=0:
niceExit=0
os.killpg(child.pid, 15)
if (time.time() - start)>(timeout+1) and timeout!=0:
niceExit=0
os.killpg(child.pid, 9)
if not niceExit and raiseExc:
raise CommandTimeoutExpired("Timeout(%s) expired for command:\n # %s\n%s" % (timeout, command, output))
if log:
log.debug("Child returncode was: %s" % str(child.returncode))
if child.returncode:
if exit_on_error:
exit(child.returncode)
if raiseExc:
raise ReturnCodeNotZero("Command failed.\nReturn code: %s\nOutput: %s" % (child.returncode, output), child.returncode)
return output
def logOutput(fds, start=0, timeout=0, print_to_stdout=False):
done = 0
output = ''
# set all fds to nonblocking
for fd in fds:
flags = fcntl.fcntl(fd, fcntl.F_GETFL)
if not fd.closed:
fcntl.fcntl(fd, fcntl.F_SETFL, flags| os.O_NONBLOCK)
while not done:
if (time.time() - start)>timeout and timeout!=0:
done = 1
break
i_rdy,o_rdy,e_rdy = select.select(fds,[],[],1)
for s in i_rdy:
# slurp as much input as is ready
string = s.read()
if string == '':
done = 1
continue
else:
if print_to_stdout:
print string
output += string
return output

1
abf/model.py Symbolic link
View file

@ -0,0 +1 @@
/home/flid/git/abf/akirilenko/python-abf/abf/model.py