Commit 699777af authored by Alain Takoudjou's avatar Alain Takoudjou

slaprunner: remove create user at first launch

It's not needed anymore to create a user at first launch of webrunner. Credential can be created from slapos recipe directly.
Webrunner can also clone default repository at first launch.

/reviewed-on !7
parent 69a41bc8
......@@ -43,6 +43,7 @@ setup(name=name,
'slapos.core', # as it provides library for slap
'xml_marshaller', # needed to dump information
'GitPython', #needed for git manipulation into slaprunner
'passlib',
'netifaces',
] + additional_install_requires,
extras_require = {
......
......@@ -11,79 +11,68 @@ from git import Repo
from flask import jsonify
def cloneRepo(data):
def cloneRepo(url, workDir, user="", email=""):
"""Clone a repository
Args:
data: a dictionary of parameters to use:
data['path'] is the path of the new project
data['repo'] is the url of the repository to be cloned
data['email'] is the user's email
data['user'] is the name of the user
Returns:
a jsonify data"""
workDir = data['path']
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.")
code = 0
json = ""
try:
if os.path.exists(workDir) and len(os.listdir(workDir)) < 2:
shutil.rmtree(workDir) # delete useless files
repo = Repo.clone_from(data["repo"], workDir)
config_writer = repo.config_writer()
config_writer.add_section("user")
if data["user"] != "":
config_writer.set_value("user", "name", data["user"].encode("utf-8"))
if data["email"] != "":
config_writer.set_value("user", "email", data["email"])
code = 1
except Exception as e:
json = safeResult(str(e))
return jsonify(code=code, result=json)
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.encode("utf-8"))
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 parsed string that contains the result of git status"""
code = 0
json = ""
try:
repo = Repo(project)
git = repo.git
json = git.status().replace('#', '')
branch = git.branch().replace(' ', '').split('\n')
isdirty = repo.is_dirty(untracked_files=True)
code = 1
except Exception as e:
json = safeResult(str(e))
return jsonify(code=code, result=json, branch=branch, dirty=isdirty)
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, name):
def switchBranch(project, branch):
"""Switch a git branch
Args:
project: directory of the local git repository
name: switch from current branch to `name` branch
Returns:
a jsonify data"""
code = 0
json = ""
try:
repo = Repo(project)
current_branch = repo.active_branch.name
if name == current_branch:
json = "This is already your active branch for this project"
else:
git = repo.git
git.checkout(name)
code = 1
except Exception as e:
json = safeResult(str(e))
return jsonify(code=code, result=json)
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):
......@@ -93,20 +82,17 @@ def addBranch(project, name, onlyCheckout=False):
name: name of the new branch
onlyCheckout: if True then the branch `name` is created before checkout
Returns:
a jsonify data"""
code = 0
json = ""
try:
repo = Repo(project)
git = repo.git
if not onlyCheckout:
git.checkout('-b', name)
else:
git.checkout(name)
code = 1
except Exception as e:
json = safeResult(str(e))
return jsonify(code=code, result=json)
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):
......
......@@ -8,12 +8,16 @@ import flask
import logging
import logging.handlers
import os
import urlparse
from slapos.htpasswd import HtpasswdFile
from slapos.runner.process import setHandler
import sys
from slapos.runner.utils import runInstanceWithLock
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')
......@@ -129,6 +133,20 @@ def serve(config):
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, e:
app.logger.warning('Error while cloning default repository: %s' % str(e))
traceback.print_exc()
app.logger.info('Running slapgrid...')
if app.config['auto_deploy_instance'] in TRUE_VALUES:
import thread
......
......@@ -707,7 +707,7 @@ a.lshare img{
}
.form{padding:10px; padding-left:20px;}
.form label{display:block; float:left; width:150px; padding-top:10px;}
.form input[type=text] ,.form input[type=password] {float:left; width:190px;margin:5px;}
.form input[type=text] ,.form input[type=password], .form select {float:left; width:190px;margin:5px;}
.hiddendiv {display: none;white-space: pre-wrap;min-height: 18px;font-size: 13px;
padding:3px;word-wrap: break-word;width:430px; max-height:120px;font-family: 'Helvetica Neue',Tahoma,Helvetica,Arial,sans-serif;}
.list{display: block;padding: 5px;background: #E7E7E7; margin-bottom:7px;}
......
......@@ -8,59 +8,56 @@ $(document).ready(function () {
$("#information").Tooltip();
$("#update").click(function () {
var haspwd = false,
hasAccount = ($("input#hasAccount").val() !== "");
if ($("input#username").val() === "" || !$("input#username").val().match(/^[\w\d\._\-]+$/)) {
var haspwd = false;
if ($("select#username").val() === "" || !$("select#username").val().match(/^[\w\d\._\-]+$/)) {
$("#error").Popup("Invalid user name. Please check it!", {type: 'alert', duration: 3000});
return false;
}
if ($("input#name").val() === "") {
$("#error").Popup("Please enter your name and surname!", {type: 'alert', duration: 3000});
$("#error").Popup("Please enter your full name for git configuration!", {type: 'alert', duration: 3000});
return false;
}
if (!$("input#email").val().match(/^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/)) {
$("#error").Popup("Please enter a valid email address!", {type: 'alert', duration: 3000});
return false;
}
if (!hasAccount && !$("input#password").val()) {
$("#error").Popup("Please enter your new password!", {type: 'alert', duration: 3000});
return false;
}
if ($("input#password").val() !== "") {
if ($("input#password").val() === "" || !$("input#password").val()) {
$("#error").Popup("Please enter your current password!", {type: 'alert', duration: 3000});
return false;
}
if ($("input#npassword").val() === "" || !$("input#npassword").val()) {
$("#error").Popup("Please enter your new password!", {type: 'alert', duration: 3000});
return false;
}
if ($("input#password").val() !== $("input#cpassword").val()) {
$("#error").Popup("your password does not match!", {type: 'alert', duration: 3000});
if ($("input#npassword").val() !== $("input#cpassword").val()) {
$("#error").Popup("your new password does not match!", {type: 'alert', duration: 3000});
return false;
}
haspwd = true;
}
if (!$("input#rcode").val().match(/^[\w\d]+$/)) {
$("#error").Popup("Please enter your password recovery code.", {type: 'alert', duration: 3000});
return false;
}
if (send) {
return false;
}
send = true;
var base_url = 'https://' + $("input#username").val() + ':'
+ $("input#password").val() + '@' + location.host
$.ajax({
type: "POST",
url: $SCRIPT_ROOT + ((hasAccount) ? '/updateAccount' : '/configAccount'),
url: $SCRIPT_ROOT + '/updateAccount',
data: {
name: $("input#name").val(),
username: $("input#username").val(),
username: $("select#username").val(),
email: $("input#email").val(),
password: ((haspwd) ? $("input#password").val() : ""),
rcode: $("input#rcode").val()
new_password: ((haspwd) ? $("input#npassword").val() : "")
},
success: function (data) {
if (data.code === 1) {
var url = 'https://' + $("input#username").val() + ':' + $("input#password").val() + '@' + location.host + $SCRIPT_ROOT + '/';
window.location.href = url;
if ($("input#npassword").val() !== "") {
var url = 'https://' + $("select#username").val() + ':' + $("input#npassword").val() + '@' + location.host + $SCRIPT_ROOT + '/';
window.location.href = url;
} else {
$("#error").Popup("Account information saved successfully!", {type: 'info', duration: 5000});
}
} else {
$("#error").Popup(data.result, {type: 'error', duration: 5000});
}
......@@ -101,10 +98,6 @@ $(document).ready(function () {
$("#error").Popup("Invalid user name. Please check it!", {type: 'alert', duration: 3000});
return false;
}
if (!$("input#new_rcode").val().match(/^[\w\d]+$/)) {
$("#error").Popup("Please enter your password recovery code.", {type: 'alert', duration: 3000});
return false;
}
if (send) {
return false;
}
......@@ -114,21 +107,19 @@ $(document).ready(function () {
url: $SCRIPT_ROOT + '/addUser',
data: {
username: $("input#new_username").val(),
password: $("input#new_password").val(),
rcode: $("input#new_rcode").val(),
password: $("input#new_password").val()
},
success: function (data) {
if (data.code === 1) {
$("#error").Popup(data.result, {type: 'info', duration: 5000});
} else if (data.code === 0) {
$("#error").Popup(data.result, {type: 'error', duration: 5000});
} else {
} else {
$("#error").Popup(data.result, {type: 'alert', duration: 5000});
}
send = false;
$("input#new_username").val('');
$("input#new_password").val('');
$("input#new_rcode").val('');
},
error: function () { send = false; }
});
......
......@@ -33,7 +33,7 @@ $(document).ready(function () {
$("#repoEmpty").hide();
$("#repoContent").show();
for (i = 0; i < result.length; i += 1) {
$("#project").append("<option value='"+result[i]+"'>"+result[i]+"</option>")
$("#project").append("<option value='"+result[i]+"'>"+result[i]+"</option>");
}
$("#project").change();
}
......@@ -144,11 +144,15 @@ $(document).ready(function () {
$("#clone").append("Clone");
send = false;
},
error: function (request, error) {
$("#error").Popup("unable to clone your project, please check your internet connection", {type: 'error', duration: 3000});
error: function (xhr, request, error) {
console.log(xhr.responseText);
$("#error").Popup("unable to clone your project, please check your internet connection.<br/>" + xhr.responseText, {type: 'error', duration: 5000});
$("#imgwaitting").hide();
$("#clone").empty();
$("#clone").append("Clone");
},
always: function () {
send = false;
}
});
return false;
......
......@@ -29,8 +29,8 @@ $(document).ready(function () {
function gitStatus() {
var project = $("#project").val(),
urldata = $("input#workdir").val() + "/" + project;
$("#status").empty();
$("#commit").hide();
$("#flash").empty();
if (project === "") {
$("#status").append("<h2>Please select one project...</h2><br/><br/>");
......@@ -55,11 +55,11 @@ $(document).ready(function () {
//alert(message);
$("#status").append("<p>" + message + "</p>");
if (data.dirty) {
$("#commit").show();
$("#status").append("<br/><h2>Display Diff for current Project</h2>");
$("#status").append("<p style='font-size:15px;'>You have changes in your project." +
$("#status").append(
"<br/><p style='font-size:15px;color: #D62B2B;padding: 0;'" +
">You have changes in your project." +
" <a href='#' id='viewdiff'"
+ ">Watch the diff file</a></p>");
+ ">Watch the diff</a></p>");
}
$("#viewdiff").click(function () {
viewDiff();
......@@ -89,7 +89,7 @@ $(document).ready(function () {
success: function (data) {
if (data.code === 1) {
$("#inline_content").empty();
$("#inline_content").append('<div class="main_content"><pre id="editorViewer"></pre></div>');
$("#inline_content").append('<div class="main_content"><pre id="editorViewer"></pre></div>');
viewer = ace.edit("editorViewer");
viewer.setTheme("ace/theme/crimson_editor");
......@@ -98,10 +98,10 @@ $(document).ready(function () {
viewer.getSession().setUseSoftTabs(true);
viewer.renderer.setHScrollBarAlwaysVisible(false);
viewer.setReadOnly(true);
$("#inlineViewer").colorbox({inline:true, width: "847px", onComplete:function(){
viewer.getSession().setValue(data.result);
}, title: 'Git diff for project ' + project});
$("#inlineViewer").click();
$("#inlineViewer").colorbox({inline:true, width: "847px", onComplete:function(){
viewer.getSession().setValue(data.result);
}, title: 'Git diff for project ' + project});
$("#inlineViewer").click();
send = false;
} else {
$("#error").Popup(data.result, {type: 'error'});
......
......@@ -9,45 +9,69 @@
{% block body %}
<div id="tabContainer">
<ul>
<li><a href="#tab1" class="active">Your personal information</a></li>
<li><a href="#tab1" class="active">Update Account information</a></li>
{% if params %}
<li><a href="#tab2">Build & Run configuration</a></li>
<li><a href="#tab3">Add user</a></li>
<li><a href="#tab2">Build & Run configuration</a></li>
{% endif %}
</ul><!-- //Tab buttons -->
<div class="tabDetails">
<div id="tab1" class="tabContents">
<form class="account">
<div class='form'>
<label for="name">Your name: </label>
<input type='text' name='name' id='name' value='{{name}}'/>
<div class='clear'></div>
<label for="email">Your email address: </label>
<input type='text' name='email' id='email' value='{{email}}'/>
<div class='clear'></div>
<label for="username">User name: </label>
<input type='text' name='username' id='username' value='{{username}}'/>
<div class='clear'></div>
<label for="password">Password: </label>
<input type='password' name='password' id='password' value=''/>
<div class='clear'></div>
<label for="cpassword">Confirm Password: </label>
<input type='password' name='cpassword' id='cpassword' value=''/>
<div class='clear'></div>
<fieldset>
<legend><b> User credentials </b> </legend>
<label for="username">Username: </label>
<select name="username" id="username">
{% for username in username_list -%}
<option value="{{username}}">{{username}}</option>
{% endfor -%}
</select>
<div class='clear'></div>
<label for="password">Password: </label>
<input type='password' name='password' id='password' value=''/>
<div class='clear'></div>
<label for="npassword">New Password: </label>
<input type='password' name='npassword' id='npassword' value=''/>
<div class='clear'></div>
<label for="cpassword">Confirm New Password: </label>
<input type='password' name='cpassword' id='cpassword' value=''/>
<div class='clear'></div>
</fieldset>
<br/>
<label for="rcode">Password Recovery code:</label>
<input type='password' name='rcode' id='rcode' value=''/>
<span class="information"><a href="#" id="information" rel="tooltip">help ?</a></span>
<div class='clear'></div>
<fieldset>
<legend><b> Git user and email </b> </legend>
<label for="name">Your name: </label>
<input type='text' name='name' id='name' value='{{name}}'/>
<div class='clear'></div>
<label for="email">Your email address: </label>
<input type='text' name='email' id='email' value='{{email}}'/>
<div class='clear'></div>
</fieldset>
<br/>
<label></label>
<input type="submit" name="update" id ="update" value="Update Account" class="button"/>
<div class='clear'></div>
</div>
<input type="hidden" name="hasAccount" id="hasAccount" value="{{name}}"/>
</form>
</div>
{% if params %}
<div id="tab3" class="tabContents">
<form class="slapgrid">
<div class='form'>
<label for="username">New user name :</label>
<input type='text' name='username' id='new_username'/>
<div class='clear'></div>
<label for="password">New password :</label>
<input type='password' name='password' id='new_password'/>
<div class='clear'></div>
<br/>
<label></label>
<input type="submit" name="add_user" id="add_user" value="Add new user" class="button"/>
<div class='clear'></div>
</div>
</form>
</div>
<div id="tab2" class="tabContents">
<form class="slapgrid">
<div class='form'>
......@@ -72,26 +96,6 @@
</div>
</form>
</div>
<div id="tab3" class="tabContents">
<form class="slapgrid">
<div class='form'>
<label for="username">New user name :</label>
<input type='text' name='username' id='new_username'/>
<div class='clear'></div>
<label for="password">New password :</label>
<input type='password' name='password' id='new_password'/>
<div class='clear'></div>
<label for="new_rcode">Password Recovery code:</label>
<input type='password' name='new_rcode' id='new_rcode' value=''/>
<span class="information"><a href="#" id="information" rel="tooltip">help ?</a></span>
<div class='clear'></div>
<br/>
<label></label>
<input type="submit" name="add_user" id="add_user" value="Add new user" class="button"/>
<div class='clear'></div>
</div>
</form>
</div>
{% endif %}
</div>
</div>
......
......@@ -83,45 +83,32 @@
Your repository folder is empty. <a id="switchtoclone">Click here</a> to create a new one!
</h2>
<div id="repoContent" style="{% if not project %}display:none{% endif %}">
<label for='project'>Current Repository: </label>
<select id="project" name="project">
{% for folder in project%}
<option value="{{folder}}">{{folder}}</option>
{% endfor %}
</select>
<a id="switchtoclone" class="lshare">&nbsp;New repository&nbsp;</a>
<br/><br/>
<div id="contentInfo">
<div id="status" style="margin-bottom:20px;">
<h2>Please select one project...</h2>
</div>
<div id="branchlist" style="margin-bottom:20px;">
<h2>Your Repository Branches</h2>
<div style="margin-left:15px;">
<label for='activebranch'>Select your active Branch: </label>
<select name="activebranch" id="activebranch">
</select>
&nbsp;&nbsp;<label for='branchname'>Branch Name: </label>
<input type="text" name="branchname" id="branchname" size='22' value="Enter the branch name..." />
<input type="submit" name="addbranch" id ="addbranch" value="Add" class="button" title='add new branch: git checkout -b branch'/>
<input type="submit" name="docheckout" id ="docheckout" value="Checkout" class="button" title='add existing branch: git checkout branch'/>
<br/>
<!--<label for='pullbranch'>Update your local repository: </label>-->
<!--<input type="submit" name="pullbranch" id ="pullbranch" value="Pull" class="button"/>-->
<!--<img class="waitting" id="pullimgwaitting" src="{{ url_for('static', filename='images/waiting.gif') }}" alt="" />-->
</div>
</div>
<div id="commit" style="margin-bottom:20px;">
<h2>Commit All your changes (On active branch)</h2>
<div style="margin-left:15px;">
<label for='commitmsg'>Commit message: </label>
<input type="text" name="commitmsg" id="commitmsg" size='40' value="Enter message..." />
<input type="submit" name="commit" id ="commitbutton" value="Commit" class="button"/>
<img class="waitting" id="imgwaitting" src="{{ url_for('static', filename='images/waiting.gif') }}" alt="" />
</div>
</div>
<br/>
</div>
<label for='project'>Current Repository: </label>
<select id="project" name="project">
{% for folder in project%}
<option value="{{folder}}">{{folder}}</option>
{% endfor %}
</select>
<a id="switchtoclone" class="lshare">&nbsp;New repository&nbsp;</a>
<br/><br/>
<div id="contentInfo">
<div id="status" style="margin-bottom:20px;">
<h2>Please select one project...</h2>
</div>
<div id="branchlist" style="margin-bottom:20px;">
<h2>Your Repository Branches</h2>
<div style="margin-left:15px;">
<label for='activebranch'>Select your active Branch: </label>
<select name="activebranch" id="activebranch">
</select>
&nbsp;&nbsp;<label for='branchname'>Branch Name: </label>
<input type="text" name="branchname" id="branchname" size='22' value="Enter the branch name..." />
<input type="submit" name="addbranch" id ="addbranch" value="Add" class="button" title='add new branch: git checkout -b branch'/>
<input type="submit" name="docheckout" id ="docheckout" value="Checkout" class="button" title='add existing branch: git checkout branch'/>
<br/>
</div>
</div>
</div>
</div>
</form>
</div>
......
......@@ -24,7 +24,8 @@ from flask import jsonify
from slapos.runner.gittools import cloneRepo
from slapos.runner.process import Popen
from slapos.htpasswd import HtpasswdFile
# from slapos.htpasswd import HtpasswdFile
from passlib.apache import HtpasswdFile
import slapos.slap
from slapos.grid.utils import md5digest
......@@ -60,61 +61,36 @@ def html_escape(text):
"""Produce entities within text."""
return "".join(html_escape_table.get(c, c) for c in text)
def getSession(config):
"""
Get the session data of current user.
Returns:
a list of user information or None if the file does not exist.
"""
user_path = os.path.join(config['etc_dir'], '.users')
user_path = os.path.join(config['etc_dir'], '.htpasswd')
if os.path.exists(user_path):
return open(user_path).read().split(';')
def checkUserCredential(config, username, password):
htpasswdfile = os.path.join(config['etc_dir'], '.htpasswd')
if not os.path.exists(htpasswdfile):
return False
passwd = HtpasswdFile(htpasswdfile)
return passwd.check_password(username, password)
def saveSession(config, account):
def updateUserCredential(config, username, password):
"""
Save account information for the current user
Args:
config: Slaprunner configuration
session: Flask session
account: New session data to be save
Returns:
True if all goes well or str (error message) if fail
"""
# XXX Cedric LN hardcoded path for files
user = os.path.join(config['etc_dir'], '.users')
htpasswdfile = os.path.join(config['etc_dir'], '.htpasswd')
backup = False
try:
if os.path.exists(user):
#backup previous data
data = open(user).read()
open('%s.back' % user, 'w').write(data)
backup = True
if not account[1]:
account[1] = data.split(';')[1]
#save new account data
open(user, 'w').write((';'.join(account)).encode("utf-8"))
# Htpasswd file for cloud9
# XXX Cedric Le N order of account list values suppose to be fixed
# Remove former file to avoid outdated accounts
if os.path.exists(htpasswdfile):
os.remove(htpasswdfile)
passwd = HtpasswdFile(htpasswdfile, create=True)
passwd.update(account[0], account[1])
if username and password:
htpasswdfile = os.path.join(config['etc_dir'], '.htpasswd')
passwd = HtpasswdFile(htpasswdfile)
passwd.set_password(username, password)
passwd.save()
return True
except Exception as e:
try:
if backup:
os.remove(user)
os.rename('%s.back' % user, user)
except:
pass
return str(e)
return False
def getRcode(config):
......@@ -125,17 +101,23 @@ def getRcode(config):
except (ConfigParser.NoSectionError, IOError) as e:
return None
def getUsernameList(config):
htpasswdfile = os.path.join(config['etc_dir'], '.htpasswd')
if os.path.exists(htpasswdfile):
passwd = HtpasswdFile(htpasswdfile)
return passwd.users()
return []
def createNewUser(config, name, passwd):
htpasswdfile = os.path.join(config['etc_dir'], '.htpasswd')
if os.path.exists(htpasswdfile):
htpasswd = HtpasswdFile(htpasswdfile)
htpasswd.update(name, passwd)
htpasswd.set_password(name, passwd)
htpasswd.save()
return True
return False