Merge branch 'develop' into create_tmp_dir

This commit is contained in:
Tom Whiston 2018-02-28 09:10:20 +01:00
commit a2c207c6d9
17 changed files with 151605 additions and 1578 deletions

View File

@ -55,6 +55,8 @@ definitions:
format: date-time
alert:
type: boolean
admin:
type: boolean
APIToken:
type: object
properties:

View File

@ -5,6 +5,7 @@ import (
"strconv"
"time"
log "github.com/Sirupsen/logrus"
"github.com/ansible-semaphore/semaphore/db"
"github.com/ansible-semaphore/semaphore/util"
"github.com/castawaylabs/mulekick"
@ -118,6 +119,13 @@ func GetTaskOutput(w http.ResponseWriter, r *http.Request) {
func RemoveTask(w http.ResponseWriter, r *http.Request) {
task := context.Get(r, "task").(db.Task)
editor := context.Get(r, "user").(*db.User)
if editor.Admin != true {
log.Warn(editor.Username + " is not permitted to delete task logs")
w.WriteHeader(http.StatusUnauthorized)
return
}
statements := []string{
"delete from task__output where task_id=?",

View File

@ -64,16 +64,38 @@ func (t *task) updateStatus() {
}
}
func (t *task) logPipe(scanner *bufio.Scanner) {
for scanner.Scan() {
t.log(scanner.Text())
func Readln(r *bufio.Reader) (string, error) {
var (
isPrefix bool = true
err error = nil
line, ln []byte
)
for isPrefix && err == nil {
line, isPrefix, err = r.ReadLine()
ln = append(ln, line...)
}
return string(ln), err
}
func (t *task) logPipe(reader *bufio.Reader) {
line, err := Readln(reader)
for err == nil {
t.log(line)
line, err = Readln(reader)
}
if err != nil && err.Error() != "EOF" {
//don't panic on this errors, sometimes it throw not dangerous "read |0: file already closed" error
fmt.Printf("Failed to read task output: %s\n", err.Error())
}
}
func (t *task) logCmd(cmd *exec.Cmd) {
stderr, _ := cmd.StderrPipe()
stdout, _ := cmd.StdoutPipe()
go t.logPipe(bufio.NewScanner(stderr))
go t.logPipe(bufio.NewScanner(stdout))
go t.logPipe(bufio.NewReader(stderr))
go t.logPipe(bufio.NewReader(stdout))
}

View File

@ -105,6 +105,12 @@ func (t *task) prepareRun() {
return
}
if err := t.runGalaxy(); err != nil {
t.log("Running galaxy failed: " + err.Error())
t.fail()
return
}
// todo: write environment
if err := t.listPlaybookHosts(); err != nil {
@ -161,12 +167,6 @@ func (t *task) run() {
t.log("Started: " + strconv.Itoa(t.task.ID))
t.log("Run task with template: " + t.template.Alias + "\n")
if err := t.runGalaxy(); err != nil {
t.log("Running galaxy failed: " + err.Error())
t.fail()
return
}
if err := t.runPlaybook(); err != nil {
t.log("Running playbook failed: " + err.Error())
t.fail()
@ -379,6 +379,7 @@ func (t *task) runPlaybook() error {
cmd.Env = t.envVars(util.Config.TmpPath, cmd.Dir, nil)
t.logCmd(cmd)
cmd.Stdin = strings.NewReader("")
return cmd.Run()
}

View File

@ -28,6 +28,13 @@ func addUser(w http.ResponseWriter, r *http.Request) {
return
}
editor := context.Get(r, "user").(*db.User)
if editor.Admin != true {
log.Warn(editor.Username + " is not permitted to create users")
w.WriteHeader(http.StatusUnauthorized)
return
}
user.Created = time.Now()
if err := db.Mysql.Insert(&user); err != nil {
@ -53,23 +60,44 @@ func getUserMiddleware(w http.ResponseWriter, r *http.Request) {
panic(err)
}
editor := context.Get(r, "user").(*db.User)
if editor.Admin != true && editor.ID != user.ID {
log.Warn(editor.Username + " is not permitted to edit users")
w.WriteHeader(http.StatusUnauthorized)
return
}
context.Set(r, "_user", user)
}
func updateUser(w http.ResponseWriter, r *http.Request) {
oldUser := context.Get(r, "_user").(db.User)
editor := context.Get(r, "user").(*db.User)
var user db.User
if err := mulekick.Bind(w, r, &user); err != nil {
return
}
if editor.Admin != true && editor.ID != oldUser.ID {
log.Warn(editor.Username + " is not permitted to edit users")
w.WriteHeader(http.StatusUnauthorized)
return
}
if editor.ID == oldUser.ID && oldUser.Admin != user.Admin {
log.Warn("User can't edit his own role")
w.WriteHeader(http.StatusUnauthorized)
return
}
if oldUser.External == true && oldUser.Username != user.Username {
log.Warn("Username is not editable for external LDAP users")
w.WriteHeader(http.StatusBadRequest)
return
}
if _, err := db.Mysql.Exec("update user set name=?, username=?, email=?, alert=? where id=?", user.Name, user.Username, user.Email, user.Alert, oldUser.ID); err != nil {
if _, err := db.Mysql.Exec("update user set name=?, username=?, email=?, alert=?, admin=? where id=?", user.Name, user.Username, user.Email, user.Alert, user.Admin, oldUser.ID); err != nil {
panic(err)
}
@ -78,10 +106,18 @@ func updateUser(w http.ResponseWriter, r *http.Request) {
func updateUserPassword(w http.ResponseWriter, r *http.Request) {
user := context.Get(r, "_user").(db.User)
editor := context.Get(r, "user").(*db.User)
var pwd struct {
Pwd string `json:"password"`
}
if editor.Admin != true && editor.ID != user.ID {
log.Warn(editor.Username + " is not permitted to edit users")
w.WriteHeader(http.StatusUnauthorized)
return
}
if user.External == true {
log.Warn("Password is not editable for external LDAP users")
w.WriteHeader(http.StatusBadRequest)
@ -102,6 +138,13 @@ func updateUserPassword(w http.ResponseWriter, r *http.Request) {
func deleteUser(w http.ResponseWriter, r *http.Request) {
user := context.Get(r, "_user").(db.User)
editor := context.Get(r, "user").(*db.User)
if editor.Admin != true && editor.ID != user.ID {
log.Warn(editor.Username + " is not permitted to delete users")
w.WriteHeader(http.StatusUnauthorized)
return
}
if _, err := db.Mysql.Exec("delete from project__user where user_id=?", user.ID); err != nil {
panic(err)

View File

@ -143,7 +143,7 @@ func doSetup() int {
user.Password = readNewline(" > Password: ", stdin)
pwdHash, _ := bcrypt.GenerateFromPassword([]byte(user.Password), 11)
if _, err := db.Mysql.Exec("insert into user set name=?, username=?, email=?, password=?, created=UTC_TIMESTAMP()", user.Name, user.Username, user.Email, pwdHash); err != nil {
if _, err := db.Mysql.Exec("insert into user set name=?, username=?, email=?, password=?, admin=1, created=UTC_TIMESTAMP()", user.Name, user.Username, user.Email, pwdHash); err != nil {
fmt.Printf(" Inserting user failed. If you already have a user, you can disregard this error.\n %v\n", err.Error())
os.Exit(1)
}

View File

@ -11,6 +11,7 @@ type User struct {
Name string `db:"name" json:"name" binding:"required"`
Email string `db:"email" json:"email" binding:"required"`
Password string `db:"password" json:"-"`
Admin bool `db:"admin" json:"admin"`
External bool `db:"external" json:"external"`
Alert bool `db:"alert" json:"alert"`
}

1
db/migrations/v2.5.0.sql Normal file
View File

@ -0,0 +1 @@
ALTER TABLE user ADD admin BOOLEAN NOT NULL DEFAULT 1 AFTER password;

View File

@ -71,5 +71,6 @@ func init() {
{Major: 2, Minor: 3, Patch: 1},
{Major: 2, Minor: 3, Patch: 2},
{Major: 2, Minor: 4},
{Major: 2, Minor: 5},
}
}

View File

@ -12,10 +12,13 @@
dd {{ task.endFormatted }}
dt Created
dd {{ task.createdFormatted }}
dt Permalink
dd
a(href="{{ task.URL }}") Output
dt Raw output
dd: input(type="checkbox" ng-model="raw" title="show logs unbesmirched")
textarea.scroll(disabled, scroll-glue) {{ output_formatted }}
textarea.scroll(readonly, scroll-glue) {{ output_formatted }}
.modal-footer
button.btn.btn-default.pull-left(ng-click="$dismiss()") Dismiss

View File

@ -1,5 +1,7 @@
h3 Task Templates
button.btn.btn-success.btn-xs.pull-right(ng-click="add()") New Template
button.btn.btn-success.btn-xs.pull-right(ng-click="add()" style="margin-left: 5px;") New Template
button.btn.btn-default.btn-xs.pull-right(ng-if="allShown && hasHiddenTemplates()" ng-click="hideHidden()") Hide Hidden
button.btn.btn-default.btn-xs.pull-right(ng-if="!allShown && hasHiddenTemplates()" ng-click="showAll()") Show Hidden
table.table.table-hover
thead: tr
@ -10,7 +12,7 @@ table.table.table-hover
th Environment
th Repository
th  
tbody: tr(ng-repeat="tpl in templates" ng-click="update(tpl)" style="cursor: pointer;")
tbody: tr(ng-repeat="tpl in templates" ng-click="update(tpl)" style="cursor: pointer;" ng-if="!tpl.hidden || allShown")
td {{ tpl.alias }}
td {{ tpl.playbook }}
td {{ sshKeysAssoc[tpl.ssh_key_id].name }}
@ -18,5 +20,7 @@ table.table.table-hover
td {{ environmentAssoc[tpl.environment_id].name }}
td {{ reposAssoc[tpl.repository_id].name }}
td: .pull-right
button.btn.btn-info.btn-xs(ng-click="copy(tpl); $event.stopPropagation();") copy
button.btn.btn-success.btn-xs(ng-click="run(tpl); $event.stopPropagation();", style="margin-left: 5px;") run
button.btn.btn-default.btn-xs(ng-if="!tpl.hidden" ng-click="hideTemplate(tpl); $event.stopPropagation();") hide
button.btn.btn-default.btn-xs(ng-if="tpl.hidden" ng-click="showTemplate(tpl); $event.stopPropagation();") show
button.btn.btn-info.btn-xs(ng-click="copy(tpl); $event.stopPropagation();" style="margin-left: 5px;") copy
button.btn.btn-success.btn-xs(ng-click="run(tpl); $event.stopPropagation();" style="margin-left: 5px;") run

View File

@ -20,6 +20,15 @@
.col-sm-6
input#password.form-control(type="password" placeholder="User Password" ng-model="user.password" required)
.form-group
.col-sm-8.col-sm-offset-4: .checkbox: label
input#admin(type="checkbox" title="User have admin privileges" ng-model="user.admin")
| Admin user
.form-group
.col-sm-8.col-sm-offset-4: .checkbox: label
input#alert(type="checkbox" title="Send email alerts about failed tasks" ng-model="user.alert")
| Send alerts
.modal-footer
button.btn.btn-default.pull-left(ng-click="$dismiss()") Dismiss
button.btn.btn-success(ng-click="$close(user)") Create User

View File

@ -1,15 +1,21 @@
.container-fluid: .row: .col-sm-12
h3.no-top-margin Users
button.btn.btn-primary.pull-right(ng-click="addUser()") New User
button.btn.btn-primary.pull-right(ng-click="addUser()" ng-if="user.admin == true") New User
table.table.table-hover
thead: tr
th Name
th Username
th Email
th Alert
th Admin
th External
tr(ng-repeat="u in users" ng-class="{ info: u.id == user.id }" ui-sref="users.user({ user_id: u.id })" style="cursor: pointer;")
td {{ u.name }}
td {{ u.username }}
td {{ u.email }}
td {{ u.alert }}
td {{ u.admin }}
td {{ u.external }}
p(ng-show="users.length == 0") No Users

View File

@ -15,6 +15,10 @@
.form-group
label.control-label.col-sm-4 Password
.col-sm-8: input.form-control(type="password" placeholder="Enter new password" ng-readonly="user.external == true" ng-model="user.password")
.form-group(ng-if="!is_self")
.col-sm-8.col-sm-offset-4: .checkbox: label
input(type="checkbox" title="User has admin privileges" ng-model="user.admin")
| Admin user
.form-group
.col-sm-8.col-sm-offset-4: .checkbox: label
input(type="checkbox" title="Send email alerts about failed tasks" ng-model="user.alert")

View File

@ -44,7 +44,7 @@ define(['controllers/projects/taskRunner'], function() {
if (!t.start || !t.end) {
return;
}
t.URL = '/api' + $scope.project.getURL() + '/tasks/' + t.id + '/output'
t.duration = moment(t.end).diff(moment(t.start), 'minutes');
});
});

View File

@ -1,5 +1,5 @@
define(['controllers/projects/taskRunner'], function () {
app.registerController('ProjectTemplatesCtrl', ['$scope', '$http', '$uibModal', 'Project', '$rootScope', function ($scope, $http, $modal, Project, $rootScope) {
app.registerController('ProjectTemplatesCtrl', ['$scope', '$http', '$uibModal', 'Project', '$rootScope', '$window', function ($scope, $http, $modal, Project, $rootScope, $window) {
$http.get(Project.getURL() + '/keys?type=ssh').success(function (keys) {
$scope.sshKeys = keys;
@ -39,8 +39,31 @@ define(['controllers/projects/taskRunner'], function () {
});
});
function getHiddenTemplates() {
try {
return JSON.parse($window.localStorage.getItem('hidden-templates') || '[]');
} catch(e) {
return [];
}
}
function setHiddenTemplates(hiddenTemplates) {
$window.localStorage.setItem('hidden-templates', JSON.stringify(hiddenTemplates));
}
$scope.hasHiddenTemplates = function() {
return getHiddenTemplates().length > 0;
}
$scope.reload = function () {
$http.get(Project.getURL() + '/templates?sort=alias&order=asc').success(function (templates) {
var hiddenTemplates = getHiddenTemplates();
for (var i = 0; i < templates.length; i++) {
var template = templates[i];
if (hiddenTemplates.indexOf(template.id) !== -1) {
template.hidden = true;
}
}
$scope.templates = templates;
});
}
@ -96,7 +119,7 @@ define(['controllers/projects/taskRunner'], function () {
swal('error', 'could not add template:' + status, 'error');
});
}).closed.then(function () {
$scope.reload();
$scope.reload();
});
}
@ -126,6 +149,33 @@ define(['controllers/projects/taskRunner'], function () {
})
}
$scope.showAll = function() {
$scope.allShown = true;
}
$scope.hideHidden = function() {
$scope.allShown = false;
}
$scope.hideTemplate = function(template) {
var hiddenTemplates = getHiddenTemplates();
if (hiddenTemplates.indexOf(template.id) === -1) {
hiddenTemplates.push(template.id);
}
setHiddenTemplates(hiddenTemplates);
template.hidden = true;
}
$scope.showTemplate = function(template) {
var hiddenTemplates = getHiddenTemplates();
var i = hiddenTemplates.indexOf(template.id);
if (i !== -1) {
hiddenTemplates.splice(i, 1);
}
setHiddenTemplates(hiddenTemplates);
delete template.hidden;
}
$scope.copy = function (template) {
var tpl = angular.copy(template);
tpl.id = null;
@ -152,4 +202,4 @@ define(['controllers/projects/taskRunner'], function () {
$scope.reload();
}]);
});
});

File diff suppressed because it is too large Load Diff