mirror of
https://github.com/semaphoreui/semaphore.git
synced 2025-01-20 15:29:28 +01:00
Merge branch 'develop' of github.com:ansible-semaphore/semaphore into develop
This commit is contained in:
commit
6ce208d0f2
@ -100,7 +100,7 @@ func resolveCapability(caps []string, resolved []string, uid string) {
|
||||
case "template":
|
||||
res, err := store.Sql().Exec(
|
||||
"insert into project__template "+
|
||||
"(project_id, inventory_id, repository_id, environment_id, alias, playbook, arguments, override_args, description, view_id) "+
|
||||
"(project_id, inventory_id, repository_id, environment_id, alias, playbook, arguments, allow_override_args_in_task, description, view_id) "+
|
||||
"values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
userProject.ID, inventoryID, repoID, environmentID, "Test-"+uid, "test-playbook.yml", "", false, "Hello, World!", view.ID)
|
||||
printError(err)
|
||||
|
@ -338,8 +338,9 @@ definitions:
|
||||
description:
|
||||
type: string
|
||||
example: Hello, World!
|
||||
override_args:
|
||||
allow_override_args_in_task:
|
||||
type: boolean
|
||||
example: false
|
||||
Template:
|
||||
type: object
|
||||
properties:
|
||||
@ -372,8 +373,9 @@ definitions:
|
||||
description:
|
||||
type: string
|
||||
example: Hello, World!
|
||||
override_args:
|
||||
allow_override_args_in_task:
|
||||
type: boolean
|
||||
example: false
|
||||
|
||||
ScheduleRequest:
|
||||
type: object
|
||||
|
@ -111,7 +111,25 @@ func UpdateKey(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := helpers.Store(r).UpdateAccessKey(key); err != nil {
|
||||
repos, err := helpers.Store(r).GetRepositories(*key.ProjectID, db.RetrieveQueryParams{})
|
||||
if err != nil {
|
||||
helpers.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, repo := range repos {
|
||||
if repo.SSHKeyID != key.ID {
|
||||
continue
|
||||
}
|
||||
err = repo.ClearCache()
|
||||
if err != nil {
|
||||
helpers.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = helpers.Store(r).UpdateAccessKey(key)
|
||||
if err != nil {
|
||||
helpers.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
@ -121,7 +139,7 @@ func UpdateKey(w http.ResponseWriter, r *http.Request) {
|
||||
desc := "Access Key " + key.Name + " updated"
|
||||
objType := db.EventKey
|
||||
|
||||
_, err := helpers.Store(r).CreateEvent(db.Event{
|
||||
_, err = helpers.Store(r).CreateEvent(db.Event{
|
||||
UserID: &user.ID,
|
||||
ProjectID: oldKey.ProjectID,
|
||||
Description: &desc,
|
||||
|
@ -4,40 +4,11 @@ import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/ansible-semaphore/semaphore/api/helpers"
|
||||
"github.com/ansible-semaphore/semaphore/db"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/ansible-semaphore/semaphore/util"
|
||||
"github.com/gorilla/context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func removeAllByPattern(path string, filenamePattern string) error {
|
||||
d, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer d.Close()
|
||||
names, err := d.Readdirnames(-1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, name := range names {
|
||||
if matched, _ := filepath.Match(filenamePattern, name); !matched {
|
||||
continue
|
||||
}
|
||||
if err := os.RemoveAll(filepath.Join(path, name)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func clearRepositoryCache(repository db.Repository) error {
|
||||
return removeAllByPattern(util.Config.TmpPath, "repository_" + strconv.Itoa(repository.ID) + "_*")
|
||||
}
|
||||
|
||||
// RepositoryMiddleware ensures a repository exists and loads it to the context
|
||||
func RepositoryMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
@ -152,7 +123,7 @@ func UpdateRepository(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if oldRepo.GitURL != repository.GitURL {
|
||||
util.LogWarning(clearRepositoryCache(oldRepo))
|
||||
util.LogWarning(oldRepo.ClearCache())
|
||||
}
|
||||
|
||||
user := context.Get(r, "user").(*db.User)
|
||||
@ -161,7 +132,7 @@ func UpdateRepository(w http.ResponseWriter, r *http.Request) {
|
||||
objType := db.EventRepository
|
||||
|
||||
_, err = helpers.Store(r).CreateEvent(db.Event{
|
||||
UserID: &user.ID,
|
||||
UserID: &user.ID,
|
||||
ProjectID: &repository.ProjectID,
|
||||
Description: &desc,
|
||||
ObjectID: &repository.ID,
|
||||
@ -201,7 +172,7 @@ func RemoveRepository(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
util.LogWarning(clearRepositoryCache(repository))
|
||||
util.LogWarning(repository.ClearCache())
|
||||
user := context.Get(r, "user").(*db.User)
|
||||
|
||||
desc := "Repository (" + repository.GitURL + ") deleted"
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -47,11 +46,11 @@ type task struct {
|
||||
}
|
||||
|
||||
func (t *task) getRepoName() string {
|
||||
return "repository_" + strconv.Itoa(t.repository.ID) + "_" + strconv.Itoa(t.template.ID)
|
||||
return t.repository.GetDirName(t.template.ID)
|
||||
}
|
||||
|
||||
func (t *task) getRepoPath() string {
|
||||
return path.Join(util.Config.TmpPath, t.getRepoName())
|
||||
return t.repository.GetPath(t.template.ID)
|
||||
}
|
||||
|
||||
func (t *task) validateRepo() error {
|
||||
@ -534,7 +533,7 @@ func (t *task) canRepositoryBePulled() bool {
|
||||
func (t *task) cloneRepository() error {
|
||||
cmd := t.makeGitCommand(util.Config.TmpPath)
|
||||
t.log("Cloning repository " + t.repository.GitURL)
|
||||
cmd.Args = append(cmd.Args, "clone", "--recursive", "--branch", t.repository.GitBranch, t.repository.GitURL, t.getRepoName())
|
||||
cmd.Args = append(cmd.Args, "clone", "--recursive", "--branch", t.repository.GitBranch, t.repository.GetGitURL(), t.getRepoName())
|
||||
t.logCmd(cmd)
|
||||
return cmd.Run()
|
||||
}
|
||||
@ -782,18 +781,25 @@ func (t *task) getPlaybookArgs() (args []string, err error) {
|
||||
if t.template.Arguments != nil {
|
||||
err = json.Unmarshal([]byte(*t.template.Arguments), &templateExtraArgs)
|
||||
if err != nil {
|
||||
t.log("Could not unmarshal arguments to []string")
|
||||
t.log("Invalid format of the template extra arguments, must be valid JSON")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if t.template.OverrideArguments {
|
||||
args = templateExtraArgs
|
||||
} else {
|
||||
args = append(args, templateExtraArgs...)
|
||||
args = append(args, playbookName)
|
||||
var taskExtraArgs []string
|
||||
|
||||
if t.template.AllowOverrideArgsInTask && t.task.Arguments != nil {
|
||||
err = json.Unmarshal([]byte(*t.task.Arguments), &taskExtraArgs)
|
||||
if err != nil {
|
||||
t.log("Invalid format of the task extra arguments, must be valid JSON")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
args = append(args, templateExtraArgs...)
|
||||
args = append(args, taskExtraArgs...)
|
||||
args = append(args, playbookName)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@ -802,6 +808,7 @@ func (t *task) setCmdEnvironment(cmd *exec.Cmd, gitSSHCommand string) {
|
||||
env = append(env, fmt.Sprintf("HOME=%s", util.Config.TmpPath))
|
||||
env = append(env, fmt.Sprintf("PWD=%s", cmd.Dir))
|
||||
env = append(env, fmt.Sprintln("PYTHONUNBUFFERED=1"))
|
||||
env = append(env, fmt.Sprintln("GIT_TERMINAL_PROMPT=0"))
|
||||
env = append(env, extractCommandEnvironment(t.environment.JSON)...)
|
||||
|
||||
if gitSSHCommand != "" {
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"github.com/ansible-semaphore/semaphore/util"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
@ -30,6 +31,7 @@ func TestPopulateDetails(t *testing.T) {
|
||||
|
||||
key, err := store.CreateAccessKey(db.AccessKey{
|
||||
ProjectID: &proj.ID,
|
||||
Type: db.AccessKeyNone,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@ -202,7 +204,7 @@ func TestTaskGetPlaybookArgs3(t *testing.T) {
|
||||
|
||||
func TestCheckTmpDir(t *testing.T) {
|
||||
//It should be able to create a random dir in /tmp
|
||||
dirName := os.TempDir() + "/" + randString(rand.Intn(10-4)+4)
|
||||
dirName := path.Join(os.TempDir(), util.RandString(rand.Intn(10-4)+4))
|
||||
err := checkTmpDir(dirName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@ -236,32 +238,3 @@ func TestCheckTmpDir(t *testing.T) {
|
||||
t.Log(err)
|
||||
}
|
||||
}
|
||||
|
||||
//HELPERS
|
||||
|
||||
//https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-golang
|
||||
var src = rand.NewSource(time.Now().UnixNano())
|
||||
|
||||
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
const (
|
||||
letterIdxBits = 6 // 6 bits to represent a letter index
|
||||
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
|
||||
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
|
||||
)
|
||||
|
||||
func randString(n int) string {
|
||||
b := make([]byte, n)
|
||||
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
|
||||
for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
|
||||
if remain == 0 {
|
||||
cache, remain = src.Int63(), letterIdxMax
|
||||
}
|
||||
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
|
||||
b[i] = letterBytes[idx]
|
||||
i--
|
||||
}
|
||||
cache >>= letterIdxBits
|
||||
remain--
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
@ -16,10 +16,13 @@ import (
|
||||
"github.com/ansible-semaphore/semaphore/util"
|
||||
)
|
||||
|
||||
type AccessKeyType string
|
||||
|
||||
const (
|
||||
AccessKeySSH = "ssh"
|
||||
AccessKeyNone = "none"
|
||||
AccessKeyLoginPassword = "login_password"
|
||||
AccessKeySSH AccessKeyType = "ssh"
|
||||
AccessKeyNone AccessKeyType = "none"
|
||||
AccessKeyLoginPassword AccessKeyType = "login_password"
|
||||
AccessKeyPAT AccessKeyType = "pat"
|
||||
)
|
||||
|
||||
// AccessKey represents a key used to access a machine with ansible from semaphore
|
||||
@ -27,7 +30,7 @@ type AccessKey struct {
|
||||
ID int `db:"id" json:"id"`
|
||||
Name string `db:"name" json:"name" binding:"required"`
|
||||
// 'ssh/login_password/none'
|
||||
Type string `db:"type" json:"type" binding:"required"`
|
||||
Type AccessKeyType `db:"type" json:"type" binding:"required"`
|
||||
|
||||
ProjectID *int `db:"project_id" json:"project_id"`
|
||||
|
||||
@ -39,6 +42,7 @@ type AccessKey struct {
|
||||
|
||||
LoginPassword LoginPassword `db:"-" json:"login_password"`
|
||||
SshKey SshKey `db:"-" json:"ssh"`
|
||||
PAT string `db:"-" json:"pat"`
|
||||
OverrideSecret bool `db:"-" json:"override_secret"`
|
||||
|
||||
InstallationKey int64 `db:"-" json:"-"`
|
||||
@ -199,9 +203,13 @@ func (key *AccessKey) SerializeSecret() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
case AccessKeyPAT:
|
||||
plaintext = []byte(key.PAT)
|
||||
case AccessKeyNone:
|
||||
key.Secret = nil
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("invalid access token type")
|
||||
}
|
||||
|
||||
encryptionString := util.Config.GetAccessKeyEncryption()
|
||||
@ -253,6 +261,8 @@ func (key *AccessKey) unmarshalAppropriateField(secret []byte) (err error) {
|
||||
if err == nil {
|
||||
key.LoginPassword = loginPass
|
||||
}
|
||||
case AccessKeyPAT:
|
||||
key.PAT = string(secret)
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -261,6 +271,7 @@ func (key *AccessKey) ResetSecret() {
|
||||
//key.Secret = nil
|
||||
key.LoginPassword = LoginPassword{}
|
||||
key.SshKey = SshKey{}
|
||||
key.PAT = ""
|
||||
}
|
||||
|
||||
func (key *AccessKey) DeserializeSecret() error {
|
||||
|
@ -51,6 +51,7 @@ func GetMigrations() []Migration {
|
||||
{Version: "2.8.20"},
|
||||
{Version: "2.8.25"},
|
||||
{Version: "2.8.26"},
|
||||
{Version: "2.8.36"},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,23 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"github.com/ansible-semaphore/semaphore/util"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type RepositorySchema string
|
||||
|
||||
const (
|
||||
RepositoryGit RepositorySchema = "git"
|
||||
RepositorySSH RepositorySchema = "ssh"
|
||||
RepositoryHTTPS RepositorySchema = "https"
|
||||
RepositoryFile RepositorySchema = "file"
|
||||
)
|
||||
|
||||
// Repository is the model for code stored in a git repository
|
||||
type Repository struct {
|
||||
ID int `db:"id" json:"id"`
|
||||
@ -13,6 +31,73 @@ type Repository struct {
|
||||
SSHKey AccessKey `db:"-" json:"-"`
|
||||
}
|
||||
|
||||
func (r Repository) ClearCache() error {
|
||||
dir, err := os.Open(util.Config.TmpPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
files, err := dir.ReadDir(0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
if !f.IsDir() {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(f.Name(), r.getDirNamePrefix()) {
|
||||
err = os.RemoveAll(path.Join(util.Config.TmpPath, f.Name()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r Repository) getDirNamePrefix() string {
|
||||
return "repository_" + strconv.Itoa(r.ID) + "_"
|
||||
}
|
||||
|
||||
func (r Repository) GetDirName(templateID int) string {
|
||||
return r.getDirNamePrefix() + strconv.Itoa(templateID)
|
||||
}
|
||||
|
||||
func (r Repository) GetPath(templateID int) string {
|
||||
return path.Join(util.Config.TmpPath, r.GetDirName(templateID))
|
||||
}
|
||||
|
||||
func (r Repository) GetGitURL() string {
|
||||
url := r.GitURL
|
||||
|
||||
if r.getSchema() == RepositoryHTTPS {
|
||||
auth := ""
|
||||
switch r.SSHKey.Type {
|
||||
case AccessKeyLoginPassword:
|
||||
auth = r.SSHKey.LoginPassword.Login + ":" + r.SSHKey.LoginPassword.Password
|
||||
case AccessKeyPAT:
|
||||
auth = r.SSHKey.PAT
|
||||
}
|
||||
if auth != "" {
|
||||
auth += "@"
|
||||
}
|
||||
url = "https://" + auth + r.GitURL[8:]
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
func (r Repository) getSchema() RepositorySchema {
|
||||
re := regexp.MustCompile(`^(\w+)://`)
|
||||
m := re.FindStringSubmatch(r.GitURL)
|
||||
if m == nil {
|
||||
return RepositoryFile
|
||||
}
|
||||
return RepositorySchema(m[1])
|
||||
}
|
||||
|
||||
func (r Repository) Validate() error {
|
||||
if r.Name == "" {
|
||||
return &ValidationError{"repository name can't be empty"}
|
||||
|
40
db/Repository_test.go
Normal file
40
db/Repository_test.go
Normal file
@ -0,0 +1,40 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"github.com/ansible-semaphore/semaphore/util"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRepository_GetSchema(t *testing.T) {
|
||||
repo := Repository{GitURL: "https://example.com/hello/world"}
|
||||
schema := repo.getSchema()
|
||||
if schema != "https" {
|
||||
t.Fatal()
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepository_ClearCache(t *testing.T) {
|
||||
util.Config = &util.ConfigType{
|
||||
TmpPath: path.Join(os.TempDir(), util.RandString(rand.Intn(10-4)+4)),
|
||||
}
|
||||
repoDir := path.Join(util.Config.TmpPath, "repository_123_55")
|
||||
err := os.MkdirAll(repoDir, 0755)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
repo := Repository{ID: 123}
|
||||
err = repo.ClearCache()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = os.Stat(repoDir)
|
||||
if err == nil {
|
||||
t.Fatal("repo directory not deleted")
|
||||
}
|
||||
if !os.IsNotExist(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
@ -27,6 +27,8 @@ type Task struct {
|
||||
|
||||
Message string `db:"message" json:"message"`
|
||||
|
||||
// CommitMessage is a git commit hash of playbook repository which
|
||||
// was active when task was created.
|
||||
CommitHash *string `db:"commit_hash" json:"commit_hash"`
|
||||
// CommitMessage contains message retrieved from git repository after checkout to CommitHash.
|
||||
// It is readonly by API.
|
||||
@ -37,6 +39,8 @@ type Task struct {
|
||||
// Version is a build version.
|
||||
// This field available only for Build tasks.
|
||||
Version *string `db:"version" json:"version"`
|
||||
|
||||
Arguments *string `db:"arguments" json:"arguments"`
|
||||
}
|
||||
|
||||
func (task *Task) GetIncomingVersion(d Store) *string {
|
||||
|
@ -49,7 +49,7 @@ type Template struct {
|
||||
// to fit into []string
|
||||
Arguments *string `db:"arguments" json:"arguments"`
|
||||
// if true, semaphore will not prepend any arguments to `arguments` like inventory, etc
|
||||
OverrideArguments bool `db:"override_args" json:"override_args"`
|
||||
AllowOverrideArgsInTask bool `db:"allow_override_args_in_task" json:"allow_override_args_in_task"`
|
||||
|
||||
Removed bool `db:"removed" json:"-"`
|
||||
|
||||
|
3
db/sql/migrations/v2.8.36.sql
Normal file
3
db/sql/migrations/v2.8.36.sql
Normal file
@ -0,0 +1,3 @@
|
||||
alter table `project__template` add allow_override_args_in_task bool not null default false;
|
||||
alter table `task` add arguments text;
|
||||
alter table `project__template` drop column `override_args`;
|
@ -16,7 +16,7 @@ func (d *SqlDb) CreateTemplate(template db.Template) (newTemplate db.Template, e
|
||||
insertID, err := d.insert(
|
||||
"id",
|
||||
"insert into project__template (project_id, inventory_id, repository_id, environment_id, "+
|
||||
"alias, playbook, arguments, override_args, description, vault_key_id, `type`, start_version,"+
|
||||
"alias, playbook, arguments, allow_override_args_in_task, description, vault_key_id, `type`, start_version,"+
|
||||
"build_template_id, view_id, autorun, survey_vars)"+
|
||||
"values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
template.ProjectID,
|
||||
@ -26,7 +26,7 @@ func (d *SqlDb) CreateTemplate(template db.Template) (newTemplate db.Template, e
|
||||
template.Alias,
|
||||
template.Playbook,
|
||||
template.Arguments,
|
||||
template.OverrideArguments,
|
||||
template.AllowOverrideArgsInTask,
|
||||
template.Description,
|
||||
template.VaultKeyID,
|
||||
template.Type,
|
||||
@ -66,7 +66,7 @@ func (d *SqlDb) UpdateTemplate(template db.Template) error {
|
||||
"alias=?, "+
|
||||
"playbook=?, "+
|
||||
"arguments=?, "+
|
||||
"override_args=?, "+
|
||||
"allow_override_args_in_task=?, "+
|
||||
"description=?, "+
|
||||
"vault_key_id=?, "+
|
||||
"`type`=?, "+
|
||||
@ -82,7 +82,7 @@ func (d *SqlDb) UpdateTemplate(template db.Template) error {
|
||||
template.Alias,
|
||||
template.Playbook,
|
||||
template.Arguments,
|
||||
template.OverrideArguments,
|
||||
template.AllowOverrideArgsInTask,
|
||||
template.Description,
|
||||
template.VaultKeyID,
|
||||
template.Type,
|
||||
@ -106,7 +106,7 @@ func (d *SqlDb) GetTemplates(projectID int, filter db.TemplateFilter, params db.
|
||||
"pt.alias",
|
||||
"pt.playbook",
|
||||
"pt.arguments",
|
||||
"pt.override_args",
|
||||
"pt.allow_override_args_in_task",
|
||||
"pt.vault_key_id",
|
||||
"pt.view_id",
|
||||
"pt.`type`").
|
||||
|
35
util/test_helpers.go
Normal file
35
util/test_helpers.go
Normal file
@ -0,0 +1,35 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
//HELPERS
|
||||
|
||||
//https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-golang
|
||||
var src = rand.NewSource(time.Now().UnixNano())
|
||||
|
||||
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
const (
|
||||
letterIdxBits = 6 // 6 bits to represent a letter index
|
||||
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
|
||||
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
|
||||
)
|
||||
|
||||
func RandString(n int) string {
|
||||
b := make([]byte, n)
|
||||
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
|
||||
for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
|
||||
if remain == 0 {
|
||||
cache, remain = src.Int63(), letterIdxMax
|
||||
}
|
||||
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
|
||||
b[i] = letterBytes[idx]
|
||||
i--
|
||||
}
|
||||
cache >>= letterIdxBits
|
||||
remain--
|
||||
}
|
||||
return string(b)
|
||||
}
|
@ -64,6 +64,13 @@
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
|
||||
<v-text-field
|
||||
v-model="item.pat"
|
||||
label="Personal access token"
|
||||
v-if="item.type === 'pat'"
|
||||
:disabled="formSaving || !canEditSecrets"
|
||||
/>
|
||||
|
||||
<v-checkbox
|
||||
v-model="item.override_secret"
|
||||
label="Override"
|
||||
@ -94,6 +101,9 @@ export default {
|
||||
}, {
|
||||
id: 'login_password',
|
||||
name: 'Login with password',
|
||||
}, {
|
||||
id: 'pat',
|
||||
name: 'Personal access tokens',
|
||||
}, {
|
||||
id: 'none',
|
||||
name: 'None',
|
||||
@ -112,6 +122,7 @@ export default {
|
||||
return {
|
||||
ssh: {},
|
||||
login_password: {},
|
||||
pat: '',
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -22,9 +22,9 @@
|
||||
>
|
||||
<div
|
||||
style="font-weight: bold;"
|
||||
>{{ commitHash ? commitHash.substr(0, 10) : '' }}
|
||||
>{{ (item.commit_hash || '').substr(0, 10) }}
|
||||
</div>
|
||||
<div v-if="commitMessage">{{ commitMessage }}</div>
|
||||
<div v-if="sourceTask && sourceTask.commit_message">{{ sourceTask.commit_message }}</div>
|
||||
</v-alert>
|
||||
|
||||
<v-select
|
||||
@ -50,7 +50,7 @@
|
||||
:key="v.name"
|
||||
:label="v.title"
|
||||
:hint="v.description"
|
||||
v-model="env[v.name]"
|
||||
v-model="editedEnvironment[v.name]"
|
||||
:required="v.required"
|
||||
:rules="[
|
||||
val => !v.required || !!val || v.title + ' is required',
|
||||
@ -58,6 +58,29 @@
|
||||
]"
|
||||
/>
|
||||
|
||||
<div class="mt-4 mb-2" v-if="!advancedOptions">
|
||||
<a @click="advancedOptions = true">
|
||||
Advanced
|
||||
<v-icon style="transform: translateY(-1px)">mdi-chevron-right</v-icon>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<codemirror
|
||||
class="mt-4"
|
||||
v-if="advancedOptions"
|
||||
:style="{ border: '1px solid lightgray' }"
|
||||
v-model="item.arguments"
|
||||
:options="cmOptions"
|
||||
placeholder='Enter extra CLI Arguments...
|
||||
Example:
|
||||
[
|
||||
"-i",
|
||||
"@myinventory.sh",
|
||||
"--private-key=/there/id_rsa",
|
||||
"-vvvv"
|
||||
]'
|
||||
/>
|
||||
|
||||
<v-row no-gutters>
|
||||
<v-col>
|
||||
<v-checkbox
|
||||
@ -76,29 +99,42 @@
|
||||
</v-form>
|
||||
</template>
|
||||
<script>
|
||||
/* eslint-disable import/no-extraneous-dependencies,import/extensions */
|
||||
|
||||
import ItemFormBase from '@/components/ItemFormBase';
|
||||
import axios from 'axios';
|
||||
import { codemirror } from 'vue-codemirror';
|
||||
import 'codemirror/lib/codemirror.css';
|
||||
import 'codemirror/mode/vue/vue.js';
|
||||
import 'codemirror/addon/lint/json-lint.js';
|
||||
import 'codemirror/addon/display/placeholder.js';
|
||||
|
||||
export default {
|
||||
mixins: [ItemFormBase],
|
||||
props: {
|
||||
templateId: Number,
|
||||
commitHash: String,
|
||||
commitMessage: String,
|
||||
buildTask: Object,
|
||||
environment: String,
|
||||
sourceTask: Object,
|
||||
},
|
||||
components: {
|
||||
codemirror,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
template: null,
|
||||
buildTasks: null,
|
||||
commitAvailable: null,
|
||||
env: null,
|
||||
editedEnvironment: null,
|
||||
cmOptions: {
|
||||
tabSize: 2,
|
||||
mode: 'application/json',
|
||||
lineNumbers: true,
|
||||
line: true,
|
||||
lint: true,
|
||||
indentWithTabs: false,
|
||||
},
|
||||
advancedOptions: false,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.env = JSON.parse(this.environment || '{}');
|
||||
},
|
||||
watch: {
|
||||
needReset(val) {
|
||||
if (val) {
|
||||
@ -110,23 +146,32 @@ export default {
|
||||
this.item.template_id = val;
|
||||
},
|
||||
|
||||
commitHash(val) {
|
||||
this.item.commit_hash = val;
|
||||
this.commitAvailable = this.item.commit_hash != null;
|
||||
},
|
||||
|
||||
version(val) {
|
||||
this.item.version = val;
|
||||
sourceTask(val) {
|
||||
this.assignItem(val);
|
||||
},
|
||||
|
||||
commitAvailable(val) {
|
||||
this.item.commit_hash = val ? this.commitHash : null;
|
||||
},
|
||||
environment(val) {
|
||||
this.env = JSON.parse(val || '{}');
|
||||
if (val == null) {
|
||||
this.commit_hash = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
assignItem(val) {
|
||||
const v = val || {};
|
||||
|
||||
if (this.item == null) {
|
||||
this.item = {};
|
||||
}
|
||||
|
||||
Object.keys(v).forEach((field) => {
|
||||
this.item[field] = v[field];
|
||||
});
|
||||
|
||||
this.editedEnvironment = JSON.parse(v.environment || '{}');
|
||||
this.commitAvailable = v.commit_hash != null;
|
||||
},
|
||||
|
||||
isLoaded() {
|
||||
return this.item != null
|
||||
&& this.template != null
|
||||
@ -134,12 +179,16 @@ export default {
|
||||
},
|
||||
|
||||
beforeSave() {
|
||||
this.item.environment = JSON.stringify(this.env);
|
||||
this.item.environment = JSON.stringify(this.editedEnvironment);
|
||||
},
|
||||
|
||||
async afterLoadData() {
|
||||
this.assignItem(this.sourceTask);
|
||||
|
||||
this.item.template_id = this.templateId;
|
||||
|
||||
this.advancedOptions = this.item.arguments != null;
|
||||
|
||||
this.template = (await axios({
|
||||
keys: 'get',
|
||||
url: `/api/project/${this.projectId}/templates/${this.templateId}`,
|
||||
@ -155,8 +204,6 @@ export default {
|
||||
if (this.buildTasks.length > 0) {
|
||||
this.item.build_task_id = this.build_task ? this.build_task.id : this.buildTasks[0].id;
|
||||
}
|
||||
|
||||
this.commitAvailable = this.commitHash != null;
|
||||
},
|
||||
|
||||
getItemsUrl() {
|
||||
|
@ -21,10 +21,7 @@
|
||||
@error="onError"
|
||||
:need-save="needSave"
|
||||
:need-reset="needReset"
|
||||
:commit-hash="sourceTask == null ? null : sourceTask.commit_hash"
|
||||
:commit-message="sourceTask == null ? null : sourceTask.commit_message"
|
||||
:build-task="sourceTask == null ? null : sourceTask.build_task"
|
||||
:environment="sourceTask == null ? null : sourceTask.environment"
|
||||
:source-task="sourceTask"
|
||||
/>
|
||||
</template>
|
||||
</EditDialog>
|
||||
|
@ -219,7 +219,8 @@
|
||||
></v-select>
|
||||
|
||||
<a @click="advancedOptions = true" v-if="!advancedOptions">
|
||||
Advanced<v-icon style="transform: translateY(-1px)">mdi-chevron-right</v-icon>
|
||||
Advanced
|
||||
<v-icon style="transform: translateY(-1px)">mdi-chevron-right</v-icon>
|
||||
</a>
|
||||
|
||||
<codemirror
|
||||
@ -237,6 +238,13 @@ Example:
|
||||
"-vvvv"
|
||||
]'
|
||||
/>
|
||||
|
||||
<v-checkbox
|
||||
v-if="advancedOptions"
|
||||
class="mt-0"
|
||||
label="Allow override in task"
|
||||
v-model="item.allow_override_args_in_task"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
@ -362,6 +370,8 @@ export default {
|
||||
})).data;
|
||||
}
|
||||
|
||||
this.advancedOptions = this.item.arguments != null || this.item.allow_override_args_in_task;
|
||||
|
||||
this.keys = (await axios({
|
||||
keys: 'get',
|
||||
url: `/api/project/${this.projectId}/keys`,
|
||||
|
Loading…
Reference in New Issue
Block a user