pax_global_header 0000666 0000000 0000000 00000000064 13721060673 0014517 g ustar 00root root 0000000 0000000 52 comment=f4b906ae97b70b808c9d1426552a6e2f006a0350
slapos.toolbox-master-slapos-runner/ 0000775 0000000 0000000 00000000000 13721060673 0020150 5 ustar 00root root 0000000 0000000 slapos.toolbox-master-slapos-runner/slapos/ 0000775 0000000 0000000 00000000000 13721060673 0021451 5 ustar 00root root 0000000 0000000 slapos.toolbox-master-slapos-runner/slapos/runner/ 0000775 0000000 0000000 00000000000 13721060673 0022762 5 ustar 00root root 0000000 0000000 slapos.toolbox-master-slapos-runner/slapos/runner/__init__.py 0000664 0000000 0000000 00000000365 13721060673 0025077 0 ustar 00root root 0000000 0000000 # See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
try:
__import__('pkg_resources').declare_namespace(__name__)
except ImportError:
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
slapos.toolbox-master-slapos-runner/slapos/runner/decorators.py 0000664 0000000 0000000 00000000373 13721060673 0025504 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# vim: set et sts=2:
#pylint: disable-all
import json
from flask import Response
def as_json(f):
def inner(*args, **kwargs):
return Response(json.dumps(f(*args, **kwargs)), mimetype='application/json')
return inner
slapos.toolbox-master-slapos-runner/slapos/runner/fileBrowser.py 0000664 0000000 0000000 00000022643 13721060673 0025626 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# vim: set et sts=2:
# pylint: disable-msg=W0311,C0301,C0103,C0111
import datetime
import hashlib
import os
import re
import shutil
from six.moves.urllib.parse import unquote
import zipfile
import fnmatch
import werkzeug
from slapos.runner.utils import realpath, tail, isText
from slapos.util import str2bytes
class FileBrowser(object):
"""This class contains all base functions for file browser"""
def __init__(self, config):
self.config = config
def _realdir(self, dir):
realdir = realpath(self.config, unquote(dir))
if not realdir:
raise NameError('Could not load directory %s: Permission denied' % dir)
return realdir
def _filterPathList(self, path_list):
"""Filter out paths that matches a list ignored file patterns.
"""
# This could be configurable ...
ignored_file_list = '''
*.py[cod]
*.o
'''
for pattern in ignored_file_list.splitlines():
if pattern.strip():
path_list = [path for path in path_list if not fnmatch.fnmatch(path, pattern)]
return path_list
def listDirs(self, dir, all=False):
"""List elements of directory 'dir' taken"""
html = 'var gsdirs = [], gsfiles = [];'
dir = unquote(dir)
# XXX-Marco 'dir' and 'all' should not shadow builtin names
realdir = realpath(self.config, dir)
if not realdir:
raise NameError('Could not load directory %s: Permission denied' % dir)
ldir = sorted(os.listdir(realdir), key=str.lower)
for f in self._filterPathList(ldir):
if f.startswith('.') and not all: # do not display this file/folder
continue
ff = os.path.join(dir, f)
realfile = os.path.join(realdir, f)
mdate = datetime.datetime.fromtimestamp(os.path.getmtime(realfile)
).strftime("%Y-%d-%m %I:%M")
md5sum = hashlib.md5(str2bytes(realfile)).hexdigest()
if not os.path.isdir(realfile):
size = os.path.getsize(realfile)
regex = re.compile("(^.*)\.(.*)", re.VERBOSE)
ext = regex.sub(r'\2', f)
if ext == f:
ext = "unknow"
else:
ext = str.lower(ext)
html += 'gsfiles.push(new gsItem("1", "%s", "%s", "%s", "%s", "%s", "%s"));' % (f, ff, size, md5sum, ext, mdate)
else:
html += 'gsdirs.push(new gsItem("2", "%s", "%s", "0", "%s", "dir", "%s"));' % (f, ff, md5sum, mdate)
return html
def fancylistDirs(self, dir, key, listfiles, all=False):
dir = unquote(dir)
realdir = realpath(self.config, dir)
if not realdir:
raise NameError('Could not load directory %s: Permission denied' % dir)
fileList = []
dirList = []
i = 0
ldir = sorted(os.listdir(realdir), key=str.lower)
for f in self._filterPathList(ldir):
if f.startswith('.') and not all: # do not display this file/folder
continue
ff = os.path.join(dir, f)
realfile = os.path.join(realdir, f)
identity = "%s%s" % (key, i)
if os.path.isdir(realfile):
dirList.append({"title": f, "key": identity,
"folder":True, "lazy":True, "path": ff})
elif listfiles:
regex = re.compile("(^.*)\.(.*)", re.VERBOSE)
ext = regex.sub(r'\2', f)
if ext == f:
ext = ""
fileList.append({"title": f, "key": identity,
"extraClasses": "ext_"+ext, "path": ff})
i+=1
return (dirList + fileList)
def makeDirectory(self, dir, filename):
"""Create a directory"""
realdir = self._realdir(dir)
folder = os.path.join(realdir, filename)
if not os.path.exists(folder):
os.mkdir(folder, 0o744)
return "{result: '1'}"
else:
return "{result: '0'}"
def makeFile(self, dir, filename):
"""Create a file in a directory dir taken"""
realdir = self._realdir(dir)
fout = os.path.join(realdir, filename)
if not os.path.exists(fout):
open(fout, 'w')
return "var responce = {result: '1'}"
else:
return "{result: '0'}"
def deleteItem(self, dir, files):
"""Delete a list of files or directories"""
# XXX-Marco do not shadow 'dir'
realdir = self._realdir(dir)
lfiles = unquote(files).split(',,,')
try:
# XXX-Marco do not shadow 'file'
for item in lfiles:
filepath = os.path.join(realdir, item)
if not item or not os.path.exists(filepath):
continue # silent skip file....
details = filepath.split('/')
last = details[-1]
if last and last.startswith('.'):
continue # cannot delete this file/directory, to prevent security
if os.path.isdir(filepath):
shutil.rmtree(filepath)
else:
os.unlink(filepath)
except Exception as e:
return str(e)
return "{result: '1'}"
def copyItem(self, dir, files, del_source=False):
"""Copy a list of files or directory to dir"""
realdir = self._realdir(dir)
lfiles = unquote(files).split(',,,')
try:
# XXX-Marco do not shadow 'file'
for file in lfiles:
realfile = realpath(self.config, file)
if not realfile:
raise NameError('Could not load file or directory %s: Permission denied' % file)
#prepare destination file
details = realfile.split('/')
dest = os.path.join(realdir, details[-1])
if os.path.exists(dest):
raise NameError('NOT ALLOWED OPERATION : File or directory already exists')
if os.path.isdir(realfile):
shutil.copytree(realfile, dest)
if del_source:
shutil.rmtree(realfile)
else:
shutil.copy(realfile, dest)
if del_source:
os.unlink(realfile)
except Exception as e:
return str(e)
return "{result: '1'}"
def rename(self, dir, filename, newfilename):
"""Rename file or directory to dir/filename"""
realdir = self._realdir(dir)
realfile = realpath(self.config, unquote(filename))
if not realfile:
raise NameError('Could not load directory %s: Permission denied' % filename)
tofile = os.path.join(realdir, newfilename)
if not os.path.exists(tofile):
os.rename(realfile, tofile)
return "{result: '1'}"
raise NameError('NOT ALLOWED OPERATION : File or directory already exists')
def copyAsFile(self, dir, filename, newfilename):
"""Copy file or directory to dir/filename"""
realdir = self._realdir(dir)
fromfile = os.path.join(realdir, filename)
tofile = os.path.join(realdir, newfilename)
if not os.path.exists(fromfile):
raise NameError('NOT ALLOWED OPERATION : File or directory does not exist')
while os.path.exists(tofile):
tofile += "_1"
shutil.copy(fromfile, tofile)
return "{result: '1'}"
def uploadFile(self, dir, files):
"""Upload a list of files in directory dir"""
realdir = self._realdir(dir)
for file in files:
if files[file]:
filename = werkzeug.secure_filename(files[file].filename)
if not os.path.exists(os.path.join(dir, filename)):
files[file].save(os.path.join(realdir, filename))
return "{result: '1'}"
def downloadFile(self, dir, filename):
"""Download file dir/filename"""
realdir = self._realdir(dir)
file = os.path.join(realdir, unquote(filename))
if not os.path.exists(file):
raise NameError('NOT ALLOWED OPERATION : File or directory does not exist %s'
% os.path.join(dir, filename))
return file
def zipFile(self, dir, filename, newfilename):
"""Add filename to archive as newfilename"""
realdir = self._realdir(dir)
tozip = os.path.join(realdir, newfilename)
fromzip = os.path.join(realdir, filename)
if not os.path.exists(fromzip):
raise NameError('NOT ALLOWED OPERATION : File or directory does not exist')
if not os.path.exists(tozip):
zip = zipfile.ZipFile(tozip, 'w', zipfile.ZIP_DEFLATED)
if os.path.isdir(fromzip):
rootlen = len(fromzip) + 1
for base, _, files in os.walk(fromzip):
for filename in files:
fn = os.path.join(base, filename).encode("utf-8")
zip.write(fn, fn[rootlen:]) # XXX can fail if 'fromzip' contains multibyte characters
else:
zip.write(fromzip)
zip.close()
return "{result: '1'}"
raise NameError('NOT ALLOWED OPERATION : File or directory already exists')
def unzipFile(self, dir, filename, newfilename):
"""Extract a zipped archive"""
realdir = self._realdir(dir)
target = os.path.join(realdir, newfilename)
archive = os.path.join(realdir, filename)
if not os.path.exists(archive):
raise NameError('NOT ALLOWED OPERATION : File or directory does not exist')
if not os.path.exists(target):
zip = zipfile.ZipFile(archive)
#member = zip.namelist()
zip.extractall(target)
#if len(member) > 1:
# zip.extractall(target)
#else:
# zip.extract(member[0], newfilename)
return "{result: '1'}"
raise NameError('NOT ALLOWED OPERATION : File or directory already exists')
def readFile(self, dir, filename, truncate=False):
"""Read file dir/filename and return content"""
realfile = realpath(self.config, os.path.join(unquote(dir),
unquote(filename)))
if not realfile:
raise NameError('Could not load directory %s: Permission denied' % dir)
if not isText(realfile):
return "FILE ERROR: Cannot display binary file, please open a text file only!"
if not truncate:
return open(realfile).read()
else:
return tail(open(realfile), 0)
slapos.toolbox-master-slapos-runner/slapos/runner/gittools.py 0000664 0000000 0000000 00000010163 13721060673 0025201 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# vim: set et sts=2:
# pylint: disable-msg=W0311,C0301,C0103,C0111
import os
import re
import shutil
from git import Repo
from flask import jsonify
def cloneRepo(url, workDir, user="", email=""):
"""Clone a repository
Args:
workDir is the path of the new project
url is the url of the repository to be cloned
email is the user's email
user is the name of the user"""
if not workDir:
return jsonify(code=0,
result="Can not create project folder.")
if os.path.exists(workDir) and len(os.listdir(workDir)) < 2:
shutil.rmtree(workDir) # delete useless files
repo = Repo.clone_from(url, workDir)
config_writer = repo.config_writer()
config_writer.add_section("user")
if user != "":
config_writer.set_value("user", "name", user.encode("utf-8"))
if email != "":
config_writer.set_value("user", "email", email)
def updateGitConfig(repository, user, email):
if not os.path.exists(repository):
return
repo = Repo(repository)
config_writer = repo.config_writer()
if user != "":
config_writer.set_value("user", "name", user)
if email != "":
config_writer.set_value("user", "email", email)
config_writer.release()
def gitStatus(project):
"""Run git status and return status of specified project folder
Args:
project: path of the projet to get status
Returns:
a list with (result of git status, current branch, isdirty)"""
repo = Repo(project)
git = repo.git
result = git.status().replace('#', '')
branch = git.branch().replace(' ', '').split('\n')
isdirty = repo.is_dirty(untracked_files=True)
return (result, branch, isdirty)
def switchBranch(project, branch):
"""Switch a git branch
Args:
project: directory of the local git repository
name: switch from current branch to `name` branch"""
repo = Repo(project)
current_branch = repo.active_branch.name
if branch == current_branch:
return False
else:
git = repo.git
git.checkout(branch)
return True
def addBranch(project, name, onlyCheckout=False):
"""Add new git branch to the repository
Args:
project: directory of the local git repository
name: name of the new branch
onlyCheckout: if True then the branch `name` is created before checkout
Returns:
True or False"""
if not os.path.exists(project):
return False
repo = Repo(project)
git = repo.git
if not onlyCheckout:
git.checkout('-b', name)
else:
git.checkout(name)
return True
def getDiff(project):
"""Get git diff for the specified project directory"""
result = ""
try:
repo = Repo(project)
git = repo.git
current_branch = repo.active_branch.name
result = git.diff(current_branch)
except Exception as e:
result = safeResult(str(e))
return result
def gitCommit(project, msg):
"""Commit changes for the specified repository
Args:
project: directory of the local repository
msg: commit message"""
code = 0
json = ""
repo = Repo(project)
if repo.is_dirty:
git = repo.git
#add file to be commited
files = repo.untracked_files
for f in files:
git.add(f)
#Commit all modified and untracked files
git.commit('-a', '-m', msg)
code = 1
else:
json = "Nothing to be commited"
return jsonify(code=code, result=json)
def gitPush(project):
"""Push changes for the specified repository
Args:
project: directory of the local repository
msg: commit message"""
code = 0
json = ""
try:
repo = Repo(project)
git = repo.git
#push changes to repo
current_branch = repo.active_branch.name
git.push('origin', current_branch)
code = 1
except Exception as e:
json = safeResult(str(e))
return jsonify(code=code, result=json)
def gitPull(project):
result = ""
code = 0
try:
repo = Repo(project)
git = repo.git
git.pull()
code = 1
except Exception as e:
result = safeResult(str(e))
return jsonify(code=code, result=result)
def safeResult(result):
"""Parse string and remove credential of the user"""
regex = re.compile("(https:\/\/)([\w\d\._-]+:[\w\d\._-]+)\@([\S]+\s)", re.VERBOSE)
return regex.sub(r'\1\3', result)
slapos.toolbox-master-slapos-runner/slapos/runner/process.py 0000664 0000000 0000000 00000003506 13721060673 0025016 0 ustar 00root root 0000000 0000000 import atexit
import os
import subprocess
SLAPRUNNER_PROCESS_LIST = []
class Popen(subprocess.Popen):
"""
Extension of Popen to launch and kill processes in a clean way
"""
def __init__(self, *args, **kwargs):
"""
Launch process and add object to process list for handler
"""
self.name = kwargs.pop('name', None)
kwargs['stdin'] = subprocess.PIPE
kwargs['stderr'] = subprocess.STDOUT
kwargs.setdefault('stdout', subprocess.PIPE)
kwargs.setdefault('close_fds', True)
subprocess.Popen.__init__(self, *args, **kwargs)
global SLAPRUNNER_PROCESS_LIST
SLAPRUNNER_PROCESS_LIST.append(self)
self.stdin.flush()
self.stdin.close()
self.stdin = None
def isRunning(name):
"""
Return True if a process with this name is running
"""
for process in SLAPRUNNER_PROCESS_LIST:
if process.name == name:
if process.poll() is None:
return True
return False
def killRunningProcess(name, recursive=False):
"""
Kill all processes with a given name
"""
for process in SLAPRUNNER_PROCESS_LIST:
if process.name == name:
process.kill(recursive=recursive)
sigterm_handled = False
def handler(*args, **kwargs):
"""
Signal handler to kill all processes
"""
global sigterm_handled
if sigterm_handled:
return
sigterm_handled = True
for process in SLAPRUNNER_PROCESS_LIST:
try:
process.kill()
except OSError:
pass
def setHandler(sig_list=None):
atexit.register(handler)
def isPidFileProcessRunning(pidfile):
"""
Test if the pidfile exist and if the process is still active
"""
if os.path.exists(pidfile):
try:
pid = int(open(pidfile, 'r').readline())
except ValueError:
pid = None
# XXX This could use psutil library.
if pid and os.path.exists("/proc/%s" % pid):
return True
return False
slapos.toolbox-master-slapos-runner/slapos/runner/run.py 0000664 0000000 0000000 00000014311 13721060673 0024140 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# vim: set et sts=2:
# pylint: disable-msg=W0311,C0301,C0103,C0111,R0904,R0903
from six.moves import configparser
import datetime
import flask
import logging
import logging.handlers
import os
from slapos.htpasswd import HtpasswdFile
from slapos.runner.process import setHandler
import sys
from slapos.runner.utils import runInstanceWithLock, startProxy
from slapos.runner.views import *
from slapos.runner.gittools import cloneRepo, switchBranch
from git import GitCommandError
import time
import traceback
TRUE_VALUES = (1, '1', True, 'true', 'True')
class Config:
def __init__(self):
self.configuration_file_path = None
self.console = None
self.log_file = None
self.logger = None
self.verbose = None
def setConfig(self):
"""
Set options given by parameters.
"""
self.configuration_file_path = os.path.abspath(os.getenv('RUNNER_CONFIG'))
# Load configuration file
configuration_parser = configparser.SafeConfigParser()
configuration_parser.read(self.configuration_file_path)
for section in ("slaprunner", "slapos", "slapproxy", "slapformat",
"sshkeys_authority", "gitclient"):
configuration_dict = dict(configuration_parser.items(section))
for key in configuration_dict:
if not getattr(self, key, None):
setattr(self, key, configuration_dict[key])
# set up logging
self.logger = logging.getLogger("slaprunner")
self.logger.setLevel(logging.INFO)
if self.console:
self.logger.addHandler(logging.StreamHandler())
self.log_file = self.log_dir + '/slaprunner.log'
if not os.path.isdir(os.path.dirname(self.log_file)):
# fallback to console only if directory for logs does not exists and
# continue to run
raise ValueError('Please create directory %r to store %r log file' % (
os.path.dirname(self.log_file), self.log_file))
else:
file_handler = logging.FileHandler(self.log_file)
file_handler.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s"))
self.logger.addHandler(file_handler)
self.logger.info('Configured logging to file %r' % self.log_file)
self.logger.info("Started.")
self.logger.info(os.environ['PATH'])
if self.verbose:
self.logger.setLevel(logging.DEBUG)
self.logger.debug("Verbose mode enabled.")
def checkHtpasswd(config):
"""XXX:set for backward compatiblity
create a htpassword if etc/.users exist"""
user = os.path.join(config['etc_dir'], '.users')
htpasswdfile = os.path.join(config['etc_dir'], '.htpasswd')
if os.path.exists(user) and not os.path.exists(htpasswdfile):
data = open(user).read().strip().split(';')
htpasswd = HtpasswdFile(htpasswdfile, create=True)
htpasswd.update(data[0], data[1])
htpasswd.save()
else:
return
def checkJSONConfig(config):
"""create a default json file with some parameters inside
if the file has never been created"""
json_file = os.path.join(config['etc_dir'], 'config.json')
if not os.path.exists(json_file):
params = {
'run_instance' : True,
'run_software' : True,
'max_run_instance' : 3,
'max_run_software' : 2
}
open(json_file, "w").write(json.dumps(params))
def run():
"Run default configuration."
# Parse arguments
config = Config()
config.setConfig()
if os.getuid() == 0:
# avoid mistakes (mainly in development mode)
raise Exception('Do not run SlapRunner as root.')
serve(config)
def serve(config):
from werkzeug.contrib.fixers import ProxyFix
workdir = os.path.join(config.runner_workdir, 'project')
software_link = os.path.join(config.runner_workdir, 'softwareLink')
app.config.update(**config.__dict__)
app.config.update(
software_log=config.software_root.rstrip('/') + '.log',
instance_log=config.instance_root.rstrip('/') + '.log',
format_log=config.instance_root.rstrip('/') + '.log',
workspace=workdir,
software_link=software_link,
instance_profile='instance.cfg',
software_profile='software.cfg',
SECRET_KEY=os.urandom(24),
PERMANENT_SESSION_LIFETIME=datetime.timedelta(days=31),
)
checkHtpasswd(app.config)
checkJSONConfig(app.config)
if not os.path.exists(workdir):
os.mkdir(workdir)
if not os.path.exists(software_link):
os.mkdir(software_link)
setHandler()
app.logger.addHandler(config.logger)
repo_url = app.config['default_repository']
branch_name = app.config.get('default_repository_branch', '')
repo_name = os.path.basename(repo_url.replace('.git', ''))
try:
repository_path = os.path.join(workdir, repo_name)
app.config.update(default_repository_path=repository_path)
if len(os.listdir(workdir)) == 0 or not os.path.exists(repository_path):
app.logger.info('cloning repository %s...' % repo_url)
result = cloneRepo(repo_url, repository_path)
if branch_name:
switchBranch(repository_path, branch_name)
except GitCommandError as e:
app.logger.warning('Error while cloning default repository: %s' % str(e))
traceback.print_exc()
# Start slapproxy here when runner is starting
app.logger.info('Stating slapproxy...')
startProxy(app.config)
app.logger.info('Running slapgrid...')
if app.config['auto_deploy_instance'] in TRUE_VALUES:
from six.moves import _thread
# XXX-Nicolas: Hack to be sure that supervisord has started
# before any communication with it, so that gunicorn doesn't exit
_thread.start_new_thread(waitForRun, (app.config,))
config.logger.info('Done.')
app.wsgi_app = ProxyFix(app.wsgi_app)
def waitForRun(config):
time.sleep(3)
runInstanceWithLock(config)
def getUpdatedParameter(self, var):
configuration_parser = configparser.SafeConfigParser()
configuration_file_path = os.path.abspath(os.getenv('RUNNER_CONFIG'))
configuration_parser.read(configuration_file_path)
for section in configuration_parser.sections():
if configuration_parser.has_option(section, var):
return configuration_parser.get(section, var)
# if the requested var is dependant of flask
if var in self.keys():
temp_dict = dict()
temp_dict.update(self)
return temp_dict[var]
else:
raise KeyError
if __name__ == 'slapos.runner.run':
flask.config.Config.__getitem__ = getUpdatedParameter
run()
slapos.toolbox-master-slapos-runner/slapos/runner/runnertest.py 0000664 0000000 0000000 00000066066 13721060673 0025563 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# vim: set et sts=2:
# pylint: disable-msg=W0311,C0301,C0103,C0111,R0904
#############################################
# !!! Attention !!!
# You now have to comment the last line
# in __init__py, wich starts the functiun
# run() in order to start the tests,
# or it will NOT work
#############################################
from __future__ import print_function
import argparse
import base64
from six.moves.configparser import SafeConfigParser
import datetime
import hashlib
import json
import os
import shutil
from . import sup_process
from io import StringIO
import ssl
import time
import unittest
from six.moves.urllib.request import Request, urlopen
import six
from slapos.runner.utils import (getProfilePath,
getSession, isInstanceRunning,
isSoftwareRunning, startProxy,
isSoftwareReleaseReady,
runSlapgridUntilSuccess, runInstanceWithLock,
getBuildAndRunParams, saveBuildAndRunParams)
from slapos.runner import views
import slapos.slap
from slapos.htpasswd import HtpasswdFile
from .run import Config
import erp5.util.taskdistribution as taskdistribution
#Helpers
def loadJson(response):
return json.loads(response.data)
def parseArguments():
"""
Parse arguments for erp5testnode-backed test.
"""
parser = argparse.ArgumentParser()
# runnertest mandatory arguments
parser.add_argument('--key_file',
metavar='KEY_FILE',
help='Path to the key file used to communicate with slapOS-master')
parser.add_argument('--cert_file',
metavar='CERT_FILE',
help='Path to the cert file used to communicate with slapOS-master')
parser.add_argument('--server_url',
metavar='SERVER_URL',
help='URL of the local slapproxy')
parser.add_argument('--computer_id',
metavar='COMPUTER_ID',
help='ID of the COMP where the slaprunner is running')
parser.add_argument('--partition_id',
metavar='PARTITION_ID',
help='ID of the partition where the slaprunner is depoyed')
# Test Node arguments
parser.add_argument('--test_result_path',
metavar='ERP5_TEST_RESULT_PATH',
help='ERP5 relative path of the test result')
parser.add_argument('--revision',
metavar='REVISION',
help='Revision of the test_suite')
parser.add_argument('--test_suite',
metavar='TEST_SUITE',
help='Name of the test suite')
parser.add_argument('--test_suite_title',
metavar='TEST_SUITE',
help='The test suite title')
parser.add_argument('--test_node_title',
metavar='NODE_TITLE',
help='Title of the testnode which is running this'
'launcher')
parser.add_argument('--project_title',
metavar='PROJECT_TITLE',
help='The project title')
parser.add_argument('--node_quantity', help='Number of parallel tests to run',
default=1, type=int)
parser.add_argument('--master_url',
metavar='TEST_SUITE_MASTER_URL',
help='Url to connect to the ERP5 Master testsuite taskditributor')
return parser.parse_args()
class SlaprunnerTestCase(unittest.TestCase):
@classmethod
def setUpClass(cls, **kw):
if len(kw) == 0:
return
cls.server_url = kw['server_url']
cls.key_file = kw['key_file']
cls.cert_file = kw['cert_file']
cls.computer_id = kw['computer_id']
cls.partition_id = kw['partition_id']
# Get parameters returned by slapos master
slap = slapos.slap.slap()
slap.initializeConnection(cls.server_url, cls.key_file, cls.cert_file)
cls.partition = slap.registerComputerPartition(
computer_guid=cls.computer_id,
partition_id=cls.partition_id
)
cls.parameter_dict = cls.partition.getConnectionParameterDict()
for attribute, value in six.iteritems(cls.parameter_dict):
setattr(cls, attribute.replace('-', '_'), value)
#create slaprunner configuration
views.app.config['TESTING'] = True
config = Config()
config.setConfig()
views.app.config.update(**config.__dict__)
cls.app = views.app.test_client()
cls.app.config = views.app.config
# Set up path (needed to find git binary)
os.environ['PATH'] = config.path
def setUp(self):
"""Initialize slapos webrunner here"""
self.users = [self.init_user, self.init_password, "slaprunner@nexedi.com", "SlapOS web runner"]
self.updateUser = ["newslapuser", "newslappwd", "slaprunner@nexedi.com", "SlapOS web runner"]
self.repo = 'https://lab.nexedi.com/rafael/slapos-workarround.git'
self.software = "workspace/slapos/software/" # relative directory fo SR
self.project = 'slapos' # Default project name
self.template = 'template.cfg'
self.partitionPrefix = 'slappart'
self.workdir = workdir = os.path.join(self.app.config['runner_workdir'], 'project')
software_link = os.path.join(self.app.config['runner_workdir'], 'softwareLink')
#update or create all runner base directory to test_dir
if not os.path.exists(workdir):
os.mkdir(workdir)
if not os.path.exists(software_link):
os.mkdir(software_link)
#Create config.json
json_file = os.path.join(views.app.config['etc_dir'], 'config.json')
if not os.path.exists(json_file):
params = {
'run_instance' : True,
'run_software' : True,
'max_run_instance' : 3,
'max_run_software' : 2
}
open(json_file, "w").write(json.dumps(params))
def tearDown(self):
"""Remove all test data"""
project = os.path.join(self.app.config['etc_dir'], '.project')
#reset tested parameters
self.updateConfigParameter('autorun', False)
self.updateConfigParameter('auto_deploy', True)
if os.path.exists(project):
os.unlink(project)
if os.path.exists(self.app.config['workspace']):
shutil.rmtree(self.app.config['workspace'])
if os.path.exists(self.app.config['software_root']):
shutil.rmtree(self.app.config['software_root'])
if os.path.exists(self.app.config['instance_root']):
shutil.rmtree(self.app.config['instance_root'])
if os.path.exists(self.app.config['software_link']):
shutil.rmtree(self.app.config['software_link'])
def updateConfigParameter(self, parameter, value):
config_parser = SafeConfigParser()
config_parser.read(os.getenv('RUNNER_CONFIG'))
for section in config_parser.sections():
if config_parser.has_option(section, parameter):
config_parser.set(section, parameter, str(value))
with open(os.getenv('RUNNER_CONFIG'), 'wb') as configfile:
config_parser.write(configfile)
def updateAccount(self, newaccount):
"""Helper for update user account data"""
return self.app.post('/updateAccount',
data=dict(
username=newaccount[0],
password=newaccount[1],
email=newaccount[2],
name=newaccount[3],
),
follow_redirects=True)
def getCurrentSR(self):
return getProfilePath(self.app.config['etc_dir'],
self.app.config['software_profile'])
def setupProjectFolder(self):
"""Helper to create a project folder as for slapos.git"""
base = os.path.join(self.app.config['workspace'], 'slapos')
software = os.path.join(base, 'software')
os.mkdir(base)
os.mkdir(software)
def setupTestSoftware(self):
"""Helper to setup Basic SR for testing purposes"""
self.setupProjectFolder()
base = os.path.join(self.app.config['workspace'], 'slapos')
software = os.path.join(base, 'software')
testSoftware = os.path.join(software, 'slaprunner-test')
sr = "[buildout]\n\n"
sr += "parts = command\n\nunzip = true\nnetworkcache-section = networkcache\n\n"
sr += "find-links += http://www.nexedi.org/static/packages/source/slapos.buildout/\n\n"
sr += "[networkcache]\ndownload-cache-url = http://www.shacache.org/shacache"
sr += "\ndownload-dir-url = http://www.shacache.org/shadir\n\n"
sr += "[command]\nrecipe = zc.recipe.egg\neggs = plone.recipe.command\n zc.buildout\n\n"
sr += """
[versions]
setuptools = 33.1.1
"""
os.mkdir(testSoftware)
open(os.path.join(testSoftware, self.app.config['software_profile']),
'w').write(sr)
md5 = hashlib.md5(os.path.join(self.app.config['workspace'],
"slapos/software/slaprunner-test",
self.app.config['software_profile'])
).hexdigest()
base = os.path.join(self.app.config['software_root'], md5)
template = os.path.join(base, self.template)
content = "[buildout]\n"
content += "parts = \n create-file\n\n"
content += "eggs-directory = %s\n" % os.path.join(base, 'eggs')
content += "develop-eggs-directory = %s\n\n" % os.path.join(base, 'develop-eggs')
content += "[create-file]\nrecipe = plone.recipe.command\n"
content += "filename = ${buildout:directory}/etc\n"
content += "command = mkdir ${:filename} && echo 'simple file' > ${:filename}/testfile\n"
os.mkdir(self.app.config['software_root'])
os.mkdir(base)
open(template, "w").write(content)
def assertCanLoginWith(self, username, password):
request = Request(self.backend_url)
base64string = base64.encodestring('%s:%s' % (username, password))[:-1]
request.add_header("Authorization", "Basic %s" % base64string)
ssl_context = ssl._create_unverified_context()
result = urlopen(request, context=ssl_context)
self.assertEqual(result.getcode(), 200)
def test_updateAccount(self):
"""test Update accound, this needs the user to log in"""
# Check that given user can log in
self.assertCanLoginWith(self.users[0], self.users[1])
# Adds a user
new_user = {'username': 'vice-president', 'password': '123456'}
response = loadJson(self.app.post('/addUser', data=new_user))
self.assertEqual(response['code'], 1)
self.assertCanLoginWith(new_user['username'], new_user['password'])
def test_cloneProject(self):
"""Start scenario 1 for deploying SR: Clone a project from git repository"""
folder = 'workspace/' + self.project
if os.path.exists(self.app.config['workspace'] + '/' + self.project):
shutil.rmtree(self.app.config['workspace'] + '/' + self.project)
data = {
'repo': self.repo,
'user': 'Slaprunner test',
'email': 'slaprunner@nexedi.com',
'name': folder
}
response = loadJson(self.app.post('/cloneRepository', data=data,
follow_redirects=True))
self.assertEqual(response['result'], "")
# Get realpath of create project
path_data = dict(file=folder)
response = loadJson(self.app.post('/getPath', data=path_data,
follow_redirects=True))
self.assertEqual(response['code'], 1)
realFolder = response['result'].split('#')[0]
#Check git configuration
config = open(os.path.join(realFolder, '.git/config')).read()
assert "slaprunner@nexedi.com" in config and "Slaprunner test" in config
# Checkout to slaprunner branch, this supposes that branch slaprunner exit
response = loadJson(self.app.post('/newBranch',
data=dict(
project=folder,
create='0',
name='erp5'
),
follow_redirects=True))
self.assertEqual(response['result'], "")
def test_createSR(self):
"""Scenario 2: Create a new software release"""
#setup project directory
self.setupProjectFolder()
newSoftware = os.path.join(self.software, 'slaprunner-test')
response = loadJson(self.app.post('/createSoftware',
data=dict(folder=newSoftware),
follow_redirects=True))
self.assertEqual(response['result'], "")
currentSR = self.getCurrentSR()
assert newSoftware in currentSR
def test_openSR(self):
"""Scenario 3: Open software release"""
self.test_cloneProject()
software = os.path.join(self.software, 'helloworld') # Drupal SR must exist in SR folder
response = loadJson(self.app.post('/setCurrentProject',
data=dict(path=software),
follow_redirects=True))
self.assertEqual(response['result'], "")
currentSR = self.getCurrentSR()
assert software in currentSR
self.assertFalse(isInstanceRunning(self.app.config))
self.assertFalse(isSoftwareRunning(self.app.config))
def test_runSoftware(self):
"""Scenario 4: CReate empty SR and save software.cfg file
then run slapgrid-sr
"""
#Call config account
#call create software Release
self.test_createSR()
newSoftware = self.getCurrentSR()
softwareRelease = "[buildout]\n\nparts =\n test-application\n"
softwareRelease += "#Test download git web repos éè@: utf-8 caracters\n"
softwareRelease += "[test-application]\nrecipe = hexagonit.recipe.download\n"
softwareRelease += "url = https://lab.nexedi.com/rafael/slapos-workarround.git\n"
softwareRelease += "filename = slapos.git\n"
softwareRelease += "download-only = true\n"
softwareRelease += "[versions]\nsetuptools = 33.1.1"
response = loadJson(self.app.post('/saveFileContent',
data=dict(file=newSoftware,
content=softwareRelease),
follow_redirects=True))
self.assertEqual(response['result'], "")
# Compile software and wait until slapgrid ends
# this is supposed to use current SR
while self.app.get('/isSRReady').data == "2":
time.sleep(2)
self.assertEqual(self.app.get('/isSRReady').data, "1")
self.assertTrue(os.path.exists(self.app.config['software_root']))
self.assertTrue(os.path.exists(self.app.config['software_log']))
assert "test-application" in open(self.app.config['software_log']).read()
sr_dir = os.listdir(self.app.config['software_root'])
self.assertEqual(len(sr_dir), 1)
createdFile = os.path.join(self.app.config['software_root'], sr_dir[0],
'parts', 'test-application', 'slapos.git')
self.assertTrue(os.path.exists(createdFile))
def test_updateInstanceParameter(self):
"""Scenario 5: Update parameters of current sofware profile"""
self.setupTestSoftware()
#Set current projet and run Slapgrid-cp
software = self.software + 'slaprunner-test/'
response = loadJson(self.app.post('/setCurrentProject',
data=dict(path=software),
follow_redirects=True))
self.assertEqual(response['result'], "")
#Send paramters for the instance
parameterDict = dict(appname='slaprunnerTest', cacountry='France')
parameterXml = '\n'
parameterXml += 'slaprunnerTest\n'
parameterXml += 'France\n'
software_type = 'production'
response = loadJson(self.app.post('/saveParameterXml',
data=dict(parameter=parameterXml,
software_type=software_type),
follow_redirects=True))
self.assertEqual(response['result'], "")
slap = slapos.slap.slap()
slap.initializeConnection(self.app.config['master_url'])
computer = slap.registerComputer(self.app.config['computer_id'])
partitionList = computer.getComputerPartitionList()
self.assertNotEqual(partitionList, [])
#Assume that the requested partition is partition 0
slapParameterDict = partitionList[0].getInstanceParameterDict()
self.assertTrue('appname' in slapParameterDict)
self.assertTrue('cacountry' in slapParameterDict)
self.assertEqual(slapParameterDict['appname'], 'slaprunnerTest')
self.assertEqual(slapParameterDict['cacountry'], 'France')
self.assertEqual(slapParameterDict['slap_software_type'], 'production')
#test getParameterXml for webrunner UI
response = loadJson(self.app.get('/getParameterXml/xml'))
self.assertEqual(parameterXml, response['result'])
response = loadJson(self.app.get('/getParameterXml/dict'))
self.assertEqual(parameterDict, response['result']['instance'])
def test_requestInstance(self):
"""Scenario 6: request software instance"""
self.test_updateInstanceParameter()
#run Software profile
response = loadJson(self.app.post('/runSoftwareProfile',
data=dict(),
follow_redirects=True))
while self.app.get('/isSRReady').data == "2":
time.sleep(2)
self.assertEqual(self.app.get('/isSRReady').data, "1")
#run instance profile
response = loadJson(self.app.post('/runInstanceProfile',
data=dict(),
follow_redirects=True))
self.assertTrue(response['result'])
# lets some time to the Instance to be deployed
time.sleep(5)
#Check that all partitions has been created
assert "create-file" in open(self.app.config['instance_log']).read()
for num in range(int(self.app.config['partition_amount'])):
partition = os.path.join(self.app.config['instance_root'],
self.partitionPrefix + str(num))
self.assertTrue(os.path.exists(partition))
#Go to partition 0
instancePath = os.path.join(self.app.config['instance_root'],
self.partitionPrefix + '0')
createdFile = os.path.join(instancePath, 'etc', 'testfile')
self.assertTrue(os.path.exists(createdFile))
assert 'simple file' in open(createdFile).read()
def test_safeAutoDeploy(self):
"""Scenario 7: isSRReady won't overwrite the existing
Sofware Instance if it has been deployed yet"""
# Test that SR won't be deployed with auto_deploy=False
self.updateConfigParameter('auto_deploy', False)
self.updateConfigParameter('autorun', False)
project = open(os.path.join(self.app.config['etc_dir'],
'.project'), "w")
project.write(self.software + 'slaprunner-test/')
project.close()
response = isSoftwareReleaseReady(self.app.config)
self.assertEqual(response, "0")
# Test if auto_deploy parameter starts the deployment of SR
self.updateConfigParameter('auto_deploy', True)
self.setupTestSoftware()
response = isSoftwareReleaseReady(self.app.config)
self.assertEqual(response, "2")
# Test that the new call to isSoftwareReleaseReady
# doesn't overwrite the previous installed one
sup_process.killRunningProcess(self.app.config, 'slapgrid-sr')
completed_path = os.path.join(self.app.config['runner_workdir'],
'softwareLink', 'slaprunner-test', '.completed')
completed_text = ".completed file: test"
completed = open(completed_path, "w")
completed.write(completed_text)
completed.close()
response = isSoftwareReleaseReady(self.app.config)
self.assertEqual(response, "1")
assert completed_text in open(completed_path).read()
def test_maximumRunOfSlapgrid(self):
"""Scenario 8: runSlapgridUntilSucces run until a defined maximum of time
slapgrid-sr and slapgrid-cp if it fails. It can also run only one or both
of them if it is defined so
We directly calls runSlapgridUntilSuccess, because we want
to test the return code of the function"""
# Installs a wrong buildout which will fail
MAX_RUN_SOFTWARE = getBuildAndRunParams(self.app.config)['max_run_software']
MAX_RUN_INSTANCE = getBuildAndRunParams(self.app.config)['max_run_instance']
self.test_createSR()
newSoftware = self.getCurrentSR()
softwareRelease = "[buildout]\n\nparts =\n test-application\n"
softwareRelease += "find-links += http://www.nexedi.org/static/packages/source/slapos.buildout/\n\n"
softwareRelease += "[networkcache]\ndownload-cache-url = http://www.shacache.org/shacache"
softwareRelease += "\ndownload-dir-url = http://www.shacache.org/shadir\n\n"
softwareRelease += "#Test download git web repos éè@: utf-8 caracters\n"
softwareRelease += "[test-application]\nrecipe = slapos.cookbook:mkdirectory\n"
softwareRelease += "test = /root/test\n"
softwareRelease += """
[versions]
setuptools = 33.1.1
"""
response = loadJson(self.app.post('/saveFileContent',
data=dict(file=newSoftware,
content=softwareRelease),
follow_redirects=True))
response = runSlapgridUntilSuccess(self.app.config, 'software')
self.assertEqual(response, MAX_RUN_SOFTWARE)
# clean folders for other tests
workdir = os.path.join(self.app.config['runner_workdir'], 'project')
git_repo = os.path.join(workdir, 'slapos')
if os.path.exists(git_repo):
shutil.rmtree(git_repo)
# Installs a software which deploys, but fails while instanciating
# preparation
base = os.path.join(self.app.config['workspace'], 'slapos')
software = os.path.join(base, 'software')
testSoftware = os.path.join(software, 'slaprunner-test')
if not os.path.exists(testSoftware):
os.makedirs(testSoftware)
software_cfg = os.path.join(testSoftware, 'software.cfg')
instance_cfg = os.path.join(testSoftware, 'instance.cfg')
# software.cfg
softwareRelease = "[buildout]\n\nparts =\n failing-template\n\n"
softwareRelease += "[failing-template]\nrecipe = hexagonit.recipe.download\n"
softwareRelease += "url = %s\n" % (instance_cfg)
softwareRelease += "destination = ${buildout:directory}\n"
softwareRelease += "download-only = true\n"
open(software_cfg, 'w+').write(softwareRelease)
# instance.cfg
content = "[buildout]\n\nparts =\n fail\n"
content += "[fail]\nrecipe=plone.recipe.command\n"
content += "command = exit 1"
open(instance_cfg, 'w+').write(content)
project = open(os.path.join(self.app.config['etc_dir'],
'.project'), "w")
project.write(self.software + 'slaprunner-test')
project.close()
# Build and Run
parameters = getBuildAndRunParams(self.app.config)
parameters['run_instance'] = False
saveBuildAndRunParams(self.app.config, parameters)
response = runSlapgridUntilSuccess(self.app.config, 'software')
self.assertEqual(response, 1)
parameters['run_instance'] = True
saveBuildAndRunParams(self.app.config, parameters)
response = runSlapgridUntilSuccess(self.app.config, 'software')
self.assertEqual(response, (1, MAX_RUN_INSTANCE))
def test_slaveInstanceDeployment(self):
"""
In order to test both slapproxy and core features of
slaprunner, will install special Software Release
into the current webrunner and fetch its instance
parameters once deployed.
"""
# XXX: This test should NOT be a unit test but should be run
# by a Test Agent running against a slapproxy.
# Deploy "test-slave-instance-deployment" Software Release
self.test_cloneProject()
software = os.path.join(self.software, 'test-slave-instance-deployment')
# Checkout to master branch
response = loadJson(self.app.post('/newBranch',
data=dict(
project=self.workdir+'/slapos',
create='0',
name='master'
),
follow_redirects=True)).get(u'code')
self.assertEqual(response, 1)
response = loadJson(self.app.post('/setCurrentProject',
data=dict(path=software),
follow_redirects=True)).get(u'code')
self.assertEqual(response, 1)
response = loadJson(self.app.post('/saveParameterXml',
data=dict(parameter='\n',
software_type='default'),
follow_redirects=True))
while self.app.get('/isSRReady').data == "2":
time.sleep(2)
self.assertEqual(self.app.get('/isSRReady').data, "1")
# Run instance deployment 3 times
runInstanceWithLock(self.app.config)
runInstanceWithLock(self.app.config)
result = runInstanceWithLock(self.app.config)
# result is True if returncode is 0 (i.e "deployed and promise passed")
self.assertTrue(result)
def test_dynamicParametersReading(self):
"""Test if the value of a parameter can change in the flask application
only by changing the value of slapos.cfg config file. This can happen when
slapgrid processes the webrunner's partition.
"""
config_file = os.path.join(self.app.config['etc_dir'], 'slapos-test.cfg')
runner_config_old = os.environ['RUNNER_CONFIG']
os.environ['RUNNER_CONFIG'] = config_file
open(config_file, 'w').write("[section]\nvar=value")
config = self.app.config
self.assertEqual(config['var'], "value")
open(config_file, 'w').write("[section]\nvar=value_changed")
self.assertEqual(config['var'], "value_changed")
# cleanup
os.environ['RUNNER_CONFIG'] = runner_config_old
class PrintStringIO(StringIO):
def write(self, data):
StringIO.write(self, data)
print(data)
def main():
"""
Function meant to be run by erp5testnode.
"""
args = parseArguments()
master = taskdistribution.TaskDistributor(args.master_url)
test_suite_title = args.test_suite_title or args.test_suite
revision = args.revision
SlaprunnerTestCase.setUpClass(
key_file=args.key_file,
cert_file=args.cert_file,
server_url=args.server_url,
computer_id=args.computer_id,
partition_id=args.partition_id)
test_result = master.createTestResult(revision, [test_suite_title],
args.test_node_title, True, test_suite_title, args.project_title)
if test_result is None:
# Thereis nothing to run here, all tests are been running by
# some other node.
return
test_line = test_result.start()
start_time = time.time()
stderr = PrintStringIO()
suite = unittest.TestLoader().loadTestsFromTestCase(SlaprunnerTestCase)
test_result = unittest.TextTestRunner(verbosity=2, stream=stderr).run(suite)
test_duration = time.time() - start_time
test_line.stop(stderr=stderr.getvalue(),
test_count=test_result.testsRun,
error_count=len(test_result.errors),
failure_count=len(test_result.failures) + len(test_result.unexpectedSuccesses),
skip_count=len(test_result.skipped),
duration=test_duration)
def runStandaloneUnitTest():
"""
Run unit tests without erp5testnode."
"""
unittest.main(module=__name__)
slapos.toolbox-master-slapos-runner/slapos/runner/static/ 0000775 0000000 0000000 00000000000 13721060673 0024251 5 ustar 00root root 0000000 0000000 slapos.toolbox-master-slapos-runner/slapos/runner/static/css/ 0000775 0000000 0000000 00000000000 13721060673 0025041 5 ustar 00root root 0000000 0000000 slapos.toolbox-master-slapos-runner/slapos/runner/static/css/colorbox.css 0000664 0000000 0000000 00000006142 13721060673 0027405 0 ustar 00root root 0000000 0000000 /*
ColorBox Core Style:
The following CSS is consistent between example themes and should not be altered.
*/
#colorbox, #cboxOverlay, #cboxWrapper{position:absolute; top:0; left:0; z-index:9999; overflow:hidden;}
#cboxOverlay{position:fixed; width:100%; height:100%;}
#cboxMiddleLeft, #cboxBottomLeft{clear:left;}
#cboxContent{position:relative;}
#cboxLoadedContent{overflow:auto;}
#cboxTitle{margin:0;}
#cboxLoadingOverlay, #cboxLoadingGraphic{position:absolute; top:0; left:0; width:100%; height:100%;}
#cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow{cursor:pointer;}
.cboxPhoto{float:left; margin:auto; border:0; display:block;}
.cboxIframe{width:100%; height:100%; display:block; border:0;}
/*
User Style:
Change the following styles to modify the appearance of ColorBox. They are
ordered & tabbed in a way that represents the nesting of the generated HTML.
*/
#cboxOverlay{background:#000;}
#colorbox{}
#cboxTopLeft{/*width:7px; height:7px; background:#000; opacity:0.6;*/}
#cboxTopCenter{/*height:7px; background:#000; opacity:0.6;*/}
#cboxTopRight{/*width:7px; height:7px; background:#000; opacity:0.6;*/}
#cboxBottomLeft{/*width:7px; height:43px; background:#000; opacity:0.6;*/}
#cboxBottomCenter{height:36px; background: #E7E6E6; /*border-bottom: 7px solid rgba(0,0,0,0.8);*/}
#cboxBottomRight{/*width:7px; height:43px; background:#000; opacity:0.6;*/}
#cboxMiddleLeft{/*width:7px; background:#000; opacity:0.6;*/}
#cboxMiddleRight{/*width:7px; background:#000; opacity:0.6;*/}
#cboxContent{background:#fff; overflow:visible;}
.cboxIframe{background:#fff;}
#cboxError{padding:50px; border:1px solid #ccc;}
#cboxLoadedContent{margin-bottom:5px;}
#cboxLoadingOverlay{background:url(images/loading_background.png) no-repeat center center;}
#cboxLoadingGraphic{background:url(images/loading.gif) no-repeat center center;}
#cboxTitle{position:absolute; bottom:-27px; left:0; text-align:left; width:100%; font-weight:bold; color:#7C7C7C; font-size:16px; margin-left: 12px;}
#cboxCurrent{position:absolute; bottom:-25px; left:58px; font-weight:bold; color:#7C7C7C;}
#cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow{position:absolute; border: none; bottom:-29px; margin-right:8px; background:url(images/controls.png) no-repeat 0px 0px; width:23px; height:23px; text-indent:-9999px;}
#cboxPrevious{left:0px; background-position: -51px -25px;}
#cboxPrevious:hover{background-position:-51px 0px;}
#cboxNext{left:27px; background-position:-75px -25px;}
#cboxNext:hover{background-position:-75px 0px;}
#cboxClose{right:0; background-position:-100px -25px;}
#cboxClose:hover{background-position:-100px 0px;}
.cboxSlideshow_on #cboxSlideshow{background-position:-125px 0px; right:27px;}
.cboxSlideshow_on #cboxSlideshow:hover{background-position:-150px 0px;}
.cboxSlideshow_off #cboxSlideshow{background-position:-150px -25px; right:27px;}
.cboxSlideshow_off #cboxSlideshow:hover{background-position:-125px 0px;} slapos.toolbox-master-slapos-runner/slapos/runner/static/css/editor.css 0000664 0000000 0000000 00000005654 13721060673 0027053 0 ustar 00root root 0000000 0000000 .editor {
margin: 0;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.box_header{
background: #E4E4E4;
width: 100%;
height: 30px;
color: #737373;
border-bottom: 4px solid #7FAED3;
min-width: 936px;
}
.box_header ul{float: left; padding-top: 2px; text-shadow: 0px 1px #F1F1F1;}
.box_header li{float: left;border: 1px solid #AAB8C2;padding: 1px 5px 1px 0;margin-left: 5px; text-indent: 5px;}
/*.box_header li:last-child{border:none}*/
.box_header li > span{cursor: pointer; height: 20px; display: block;line-height: 20px;font-weight: bold;padding: 1px;}
.box_header li:hover{border: 1px solid #57A1D6;background-color: #C7C7C7;}
.box_header li:last-child{margin-right: 5px;}
.box_header li>a{font-weight: bold; font-size:1em;display: block;padding: 2px;}
.save_btn{background: url(../images/icon_save.png) center right no-repeat;width: 25px;}
.expand_editor{background: url(../images/fullscreen.png) center right no-repeat;width: 23px;}
.e_expanded{background: url(../images/fullscreen_exit.png) center right no-repeat;}
.swith_btn{background: url(../images/gnome-session-switch.png) center right no-repeat;width: 105px;}
.flist_btn{background: url(../images/list2_down.png) center right no-repeat;width: 26px;}
.fmenu_btn{background: url(../images/ui_menu_blue.png) center right no-repeat;width: 58px;}
.shell_btn{background: url(../images/terminal.png) center right no-repeat;width: 62px;}
#tabControl{overflow: hidden;}
#tabControl .item{float:left; min-width: 60px; background: #D5D5D5; height: 22px; padding-top: 8px; font-size:1em;
border-left:1px #E4E4E4 solid; cursor:pointer; color: #5C7077; text-shadow: 0px 1px #E6E6E6; position: relative;}
#tabControl .item:hover{background: #C7C7C7;}
#tabControl.item:last-child{margin-right:none; overflow: hidden}
#tabControl .active{background: #7FAED3; color: #fff; text-shadow: none;}
#tabControl .active:hover{background: #7FAED3;}
#tabControl .item span{padding: 0 4px; display: block; float: left; text-overflow:clip; max-width: 126px; white-space:nowrap; overflow: hidden;}
#tabControl .active span.bt_close{color: #DBDBDB;}
#tabControl .active span.bt_close:hover{color: #fff;}
#tabContent pre {display: none;}
#tabContent pre.active {display: block;}
.item-hide{display:none;}
#shell-window {
width: 974px;
height: 350px;
z-index: 10;
border: 2px solid #6A93A0;
border-top: none;
background: #9bbed6;
position: absolute;
display: none;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
}
#shell-result {
width: 98%;
height: 300px;
margin-left: 1%;
margin-right: 1%;
margin-top: 5px;
padding: 3px;
background: #E4E4E4;
color: #074A86;
box-sizing: border-box;
border: 2px inset #6A93A0;
overflow: auto;
}
.runned-command
{
font-weight: bold;
}
#shell-input {
width: 96%;
margin-left: 1%;
margin-right: 1%;
position: absolute;
bottom: 10px;
background: #E4E4E4;
color: #074A86;
border: inset 1px #678dad;
}
slapos.toolbox-master-slapos-runner/slapos/runner/static/css/gsFileManager.css 0000664 0000000 0000000 00000020560 13721060673 0030262 0 ustar 00root root 0000000 0000000 UL.jqueryFileTree {
font-family: Verdana, sans-serif;
font-size: 12px;
line-height: 18px;
padding: 0px;
margin: 0px;
}
UL.jqueryFileTree LI {
list-style: none;
padding: 0px;
padding-left: 20px;
margin: 0px;
white-space: nowrap;
}
UL.jqueryFileTree A {
color: #333;
text-decoration: none;
}
UL.jqueryFileTree A:hover {
background: #BDF;
}
.gs_dir_list{display: none;}
.gs_dir_content {
height: 100%;
width: 100%;
}
.gs_dir_content_menu {
padding: 0 0 0 5px;
margin-bottom: 5px;
border: 1px solid #BBBBBB;
background-color: white;
}
.gs_dir_content_button {
background: url("data:image/gif;base64,R0lGODlhKAAVAJEAAPn5+e7u7uPj4////yH5BAEAAAMALAAAAAAoABUAAAIxhI+py+0Po5y02ouz3rz7FoTiSJbmiabqGgruC8fyTNf2jef6zvf+DwwKh8Si8SgsAAA7") repeat-x scroll 0 0 transparent;
border: 1px outset #BBBBBB;
cursor: pointer;
margin-right: 7px;
font-weight: bold;
padding: 1px;
background-position: 0, 50;
text-decoration: none;
color: black;
}
.gs_dir_content_button:hover {
color: #EF7929;
border: 1px inset #BBBBBB;
}
.gs_dir_content_files {
min-height: 250px;
max-height: 400px;
overflow: auto;
background-color: white;
border: 1px solid #BBBBBB;
}
div.browser div.gs_dir_content_files{min-height:330px;}
#curDir {
height: 30px;
line-height: 30px;
}
#gsClipBoard{display: none;}
.directory_info {
padding-left: 20px;
}
.directory_info a {
text-decoration: none;
}
#rootLink {
display: none;
}
.loadingDiv {
width: 100%;
height: 75px;
margin-top: 50px;
background: url(images/loading.gif) center center no-repeat;
}
.demoto {
width: 100%;
height: 100%;
}
.dir_index {
cursor: pointer;
}
.toggleplus {
background: url('images/toggle_plus.png') left top no-repeat;
}
.toggleminus {
background: url('images/toggle_minus.png') left top no-repeat;
}
.dirs_files_table {
width: 100%;
font-size: 12px;
}
.dirs_files_table td{padding: 0;border:none}
.gsItem {padding: 4px; padding-left: 27px;}
.file_ext_name {
font-weight: normal;
color: #FF6600;
}
.gsHeadTable {
width :100%;
}
.dirs_files_table tr:hover {background: #BDF;}
.gsHeadText {
color:#666;
height:34px;
font-weight: bold;
}
.gs_head{padding: 10px 5px 10px 0;}
.gs_title{padding-left: 10px; margin:5px 5px 0 0; background: #e4e4e4; height:30px;}
.rowSelected {
color: #FFF;
background-color: #c2c2c2;
}
.rowSelected a {
color: #FFF;
}
.pathlink {cursor: pointer; font-size: 13px; margin-right: 2px; margin-left: 2px;}
.gs_delimiter {
height: 16px;
line-height: 16px;
}
/* Core Styles */
.directory {
background-image:url(images/directory.png);
background-repeat:no-repeat;
}
.directoryMeny a { background: url(images/directory.png) left top no-repeat; padding-left: 20px}
/*.jqueryFileTree LI.expanded { background: url(images/folder_open.png) 20px top no-repeat; }*/
.file {
background-image:url(images/file.png);
background-repeat:no-repeat;
cursor:pointer;
margin-left: 5px;
margin-top: 2px;
padding-left: 22px;
}
.jqueryFileTree LI.wait { background: url(images/spinner.gif) left top no-repeat; }
/* File Extensions*/
.ext_3gp {
background-image: url(images/film.png) left top no-repeat;
}
.ext_afp {
background-image: url(images/code.png); }
.ext_afpa {
background-image: url(images/code.png); }
.ext_asp {
background-image: url(images/code.png); }
.ext_aspx {
background-image: url(images/code.png); }
.ext_avi {
background-image: url(images/film.png); }
.ext_bat {
background-image: url(images/application.png); }
.ext_bmp {
background-image: url(images/picture.png); }
.ext_c {
background-image: url(images/code.png); }
.ext_cfm {
background-image: url(images/code.png); }
.ext_cgi {
background-image: url(images/code.png); }
.ext_com {
background-image: url(images/application.png); }
.ext_cpp {
background-image: url(images/code.png); }
.ext_css {
background-image: url(images/css.png); }
.ext_doc {
background-image: url(images/doc.png); }
.ext_exe {
background-image: url(images/application.png); }
.ext_gif {
background-image: url(images/picture.png);
}
.ext_fla {
background-image: url(images/flash.png); }
.ext_h {
background-image: url(images/code.png); }
.ext_htm {
background-image: url(images/html.png); }
.ext_html {
background-image: url(images/html.png); }
.ext_jar {
background-image: url(images/java.png); }
.ext_jpg {
background-image: url(images/picture.png);
}
.ext_jpeg {
background-image: url(images/picture.png); }
.ext_js {
background-image: url(images/script.png) ; }
.ext_lasso {
background-image: url(images/code.png) ; }
.ext_log {
background-image: url(images/txt.png) ; }
.ext_m4p {
background-image: url(images/music.png); }
.ext_mov {
background-image: url(images/film.png); }
.ext_mp3 {
background-image: url(images/music.png); }
.ext_mp4 {
background-image: url(images/film.png); }
.ext_mpg {
background-image: url(images/film.png); }
.ext_mpeg {
background-image: url(images/film.png); }
.ext_ogg {
background-image: url(images/music.png); }
.ext_pcx {
background-image: url(images/picture.png); }
.ext_pdf {
background-image: url(images/pdf.png); }
.ext_php {
background-image: url(images/php.png); }
.ext_png {
background-image: url(images/picture.png); }
.ext_ppt {
background-image: url(images/ppt.png); }
.ext_psd {
background-image: url(images/psd.png); }
.ext_pl {
background-image: url(images/script.png);; }
.ext_py {
background-image: url(images/script.png) ;}
.ext_rb {
background-image: url(images/ruby.png); }
.ext_rbx {
background-image: url(images/ruby.png); }
.ext_rhtml {
background-image: url(images/ruby.png); }
.ext_rpm {
background-image: url(images/linux.png); }
.ext_ruby {
background-image: url(images/ruby.png); }
.ext_sql {
background-image: url(images/db.png); }
.ext_swf {
background-image: url(images/flash.png); }
.ext_tif {
background-image: url(images/picture.png); }
.ext_tiff {
background-image: url(images/picture.png); }
.ext_txt {
background-image: url(images/txt.png) ; }
.ext_vb {
background-image: url(images/code.png) ; }
.ext_wav {
background-image: url(images/music.png) ; }
.ext_wmv {
background-image: url(images/film.png); }
.ext_xls {
background-image: url(images/xls.png) ; }
.ext_xml {
background-image: url(images/code.png) ; }
.ext_zip {
background-image: url(images/zip.png) ; }
.ext_cfg {
background-image: url(images/cfg.png) ; }
/* Generic context menu styles */
.contextMenu {
position: absolute;
width: 160px;
z-index: 99999;
border: solid 1px #CCC;
background: #ffffff;
padding: 0px;
margin: 0px;
display: none;
box-shadow: 2px 2px 6px rgba(0,0,0,.2);
}
.contextMenu LI {
list-style: none;
padding: 0px;
margin: 0px;
border: none;
}
.contextMenu A {
color: #333;
text-decoration: none;
display: block;
line-height: 20px;
height: 20px;
background-position: 6px center;
background-repeat: no-repeat;
outline: none;
padding: 1px 5px;
padding-left: 28px;
}
.contextMenu LI.hover A {
color: #FFF;
background-color: #3399FF;
}
.contextMenu LI.disabled A {
color: #AAA;
cursor: default;
}
.contextMenu LI.hover.disabled A {
background-color: transparent;
}
.contextMenu LI.separator {
border-top: solid 1px #CCC;
}
/*
Adding Icons
You can add icons to the context menu by adding
classes to the respective LI element(s)
*/
.contextMenu LI.edit A { background-image: url(images/page_white_edit.png); }
.contextMenu LI.cut A { background-image: url(images/cut.png); }
.contextMenu LI.copy A { background-image: url(images/page_white_copy.png); }
.contextMenu LI.paste A { background-image: url(images/page_white_paste.png); }
.contextMenu LI.delete A { background-image: url(images/cross.png); }
.contextMenu LI.quit A { background-image: url(images/door.png); }
.contextMenu LI.directorymenu A { background-image: url(images/folder_open.png); }
.contextMenu LI.rename A { background-image: url(images/mfile.png); }
.contextMenu LI.download A { background-image: url(images/disk.png); }
.contextMenu LI.notepad A { background-image: url(images/page_white_edit.png); }
.contextMenu LI.picture A { background-image: url(images/mpicture.png); }
.contextMenu LI.newfile A { background-image: url(images/new_file.png); }
.contextMenu LI.newdir A { background-image: url(images/new_dir.png); }
.contextMenu LI.uploadfolder A { background-image: url(images/upload_folder.png); }
.contextMenu LI.selection A { background-image: url(images/selection-select.png); }
.contextMenu LI.zip A { background-image: url(images/mzip.png); } slapos.toolbox-master-slapos-runner/slapos/runner/static/css/images/ 0000775 0000000 0000000 00000000000 13721060673 0026306 5 ustar 00root root 0000000 0000000 slapos.toolbox-master-slapos-runner/slapos/runner/static/css/images/.directory 0000664 0000000 0000000 00000000101 13721060673 0030303 0 ustar 00root root 0000000 0000000 [Dolphin]
ShowPreview=true
Timestamp=2012,1,11,18,9,34
Version=2
slapos.toolbox-master-slapos-runner/slapos/runner/static/css/images/application.png 0000664 0000000 0000000 00000000720 13721060673 0031316 0 ustar 00root root 0000000 0000000 PNG
IHDR a gAMA 7 tEXtSoftware Adobe ImageReadyqe<