feat: remove soft delete functionality

This commit is contained in:
Denis Gukov 2022-02-03 12:05:13 +05:00
parent 6cc3d0f250
commit 05dd7c5653
55 changed files with 994 additions and 527 deletions

View File

@ -39,7 +39,7 @@ aliases:
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.5/install.sh | bash
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
nvm install 12.15.0 && nvm alias default 12.15.0
nvm install 12.15.0 && nvm name default 12.15.0
# Each step uses the same `$BASH_ENV`, so need to modify it
echo 'export NVM_DIR="$HOME/.nvm"' >> $BASH_ENV
echo "[ -s \"$NVM_DIR/nvm.sh\" ] && . \"$NVM_DIR/nvm.sh\"" >> $BASH_ENV

View File

@ -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, allow_override_args_in_task, description, view_id) "+
"(project_id, inventory_id, repository_id, environment_id, name, 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)

3
.gitignore vendored
View File

@ -14,7 +14,8 @@ web2/dist/**/*
.DS_Store
node_modules/
.idea/
/.idea/
/semaphore.iml
/bin/
*-packr.go

View File

@ -326,7 +326,7 @@ definitions:
view_id:
type: integer
minimum: 1
alias:
name:
type: string
example: Test
playbook:
@ -361,7 +361,7 @@ definitions:
view_id:
type: integer
minimum: 1
alias:
name:
type: string
example: Test
playbook:
@ -1221,7 +1221,7 @@ paths:
required: true
type: string
description: sorting name
enum: [alias, playbook, ssh_key, inventory, environment, repository]
enum: [name, playbook, ssh_key, inventory, environment, repository]
- name: order
in: query
required: true

View File

@ -31,6 +31,17 @@ func EnvironmentMiddleware(next http.Handler) http.Handler {
})
}
func GetEnvironmentRefs(w http.ResponseWriter, r *http.Request) {
env := context.Get(r, "environment").(db.Environment)
refs, err := helpers.Store(r).GetEnvironmentRefs(env.ProjectID, env.ID)
if err != nil {
helpers.WriteError(w, err)
return
}
helpers.WriteJSON(w, http.StatusOK, refs)
}
// GetEnvironment retrieves sorted environments from the database
func GetEnvironment(w http.ResponseWriter, r *http.Request) {
@ -61,9 +72,9 @@ func UpdateEnvironment(w http.ResponseWriter, r *http.Request) {
}
if env.ID != oldEnv.ID {
helpers.WriteJSON(w, http.StatusBadRequest, map[string]string{
"error": "Environment ID in body and URL must be the same",
})
helpers.WriteJSON(w, http.StatusBadRequest, map[string]string{
"error": "Environment ID in body and URL must be the same",
})
return
}
@ -109,7 +120,7 @@ func AddEnvironment(w http.ResponseWriter, r *http.Request) {
desc := "Environment " + newEnv.Name + " created"
_, err = helpers.Store(r).CreateEvent(db.Event{
UserID: &user.ID,
UserID: &user.ID,
ProjectID: &newEnv.ID,
ObjectType: &objType,
ObjectID: &newEnv.ID,
@ -129,19 +140,13 @@ func RemoveEnvironment(w http.ResponseWriter, r *http.Request) {
var err error
softDeletion := r.URL.Query().Get("setRemoved") == "1"
if softDeletion {
err = helpers.Store(r).DeleteEnvironmentSoft(env.ProjectID, env.ID)
} else {
err = helpers.Store(r).DeleteEnvironment(env.ProjectID, env.ID)
if err == db.ErrInvalidOperation {
helpers.WriteJSON(w, http.StatusBadRequest, map[string]interface{}{
"error": "Environment is in use by one or more templates",
"inUse": true,
})
return
}
err = helpers.Store(r).DeleteEnvironment(env.ProjectID, env.ID)
if err == db.ErrInvalidOperation {
helpers.WriteJSON(w, http.StatusBadRequest, map[string]interface{}{
"error": "Environment is in use by one or more templates",
"inUse": true,
})
return
}
if err != nil {

View File

@ -34,6 +34,17 @@ func InventoryMiddleware(next http.Handler) http.Handler {
})
}
func GetInventoryRefs(w http.ResponseWriter, r *http.Request) {
inventory := context.Get(r, "inventory").(db.Inventory)
refs, err := helpers.Store(r).GetInventoryRefs(inventory.ProjectID, inventory.ID)
if err != nil {
helpers.WriteError(w, err)
return
}
helpers.WriteJSON(w, http.StatusOK, refs)
}
// GetInventory returns an inventory from the database
func GetInventory(w http.ResponseWriter, r *http.Request) {
if inventory := context.Get(r, "inventory"); inventory != nil {
@ -180,19 +191,13 @@ func RemoveInventory(w http.ResponseWriter, r *http.Request) {
inventory := context.Get(r, "inventory").(db.Inventory)
var err error
softDeletion := r.URL.Query().Get("setRemoved") == "1"
if softDeletion {
err = helpers.Store(r).DeleteInventorySoft(inventory.ProjectID, inventory.ID)
} else {
err = helpers.Store(r).DeleteInventory(inventory.ProjectID, inventory.ID)
if err == db.ErrInvalidOperation {
helpers.WriteJSON(w, http.StatusBadRequest, map[string]interface{}{
"error": "Inventory is in use by one or more templates",
"inUse": true,
})
return
}
err = helpers.Store(r).DeleteInventory(inventory.ProjectID, inventory.ID)
if err == db.ErrInvalidOperation {
helpers.WriteJSON(w, http.StatusBadRequest, map[string]interface{}{
"error": "Inventory is in use by one or more templates",
"inUse": true,
})
return
}
if err != nil {

View File

@ -30,6 +30,17 @@ func KeyMiddleware(next http.Handler) http.Handler {
})
}
func GetKeyRefs(w http.ResponseWriter, r *http.Request) {
key := context.Get(r, "accessKey").(db.AccessKey)
refs, err := helpers.Store(r).GetAccessKeyRefs(*key.ProjectID, key.ID)
if err != nil {
helpers.WriteError(w, err)
return
}
helpers.WriteJSON(w, http.StatusOK, refs)
}
// GetKeys retrieves sorted keys from the database
func GetKeys(w http.ResponseWriter, r *http.Request) {
if key := context.Get(r, "accessKey"); key != nil {
@ -161,19 +172,13 @@ func RemoveKey(w http.ResponseWriter, r *http.Request) {
var err error
softDeletion := r.URL.Query().Get("setRemoved") == "1"
if softDeletion {
err = helpers.Store(r).DeleteAccessKeySoft(*key.ProjectID, key.ID)
} else {
err = helpers.Store(r).DeleteAccessKey(*key.ProjectID, key.ID)
if err == db.ErrInvalidOperation {
helpers.WriteJSON(w, http.StatusBadRequest, map[string]interface{}{
"error": "Access Key is in use by one or more templates",
"inUse": true,
})
return
}
err = helpers.Store(r).DeleteAccessKey(*key.ProjectID, key.ID)
if err == db.ErrInvalidOperation {
helpers.WriteJSON(w, http.StatusBadRequest, map[string]interface{}{
"error": "Access Key is in use by one or more templates",
"inUse": true,
})
return
}
if err != nil {

View File

@ -30,6 +30,17 @@ func RepositoryMiddleware(next http.Handler) http.Handler {
})
}
func GetRepositoryRefs(w http.ResponseWriter, r *http.Request) {
repo := context.Get(r, "repository").(db.Repository)
refs, err := helpers.Store(r).GetRepositoryRefs(repo.ProjectID, repo.ID)
if err != nil {
helpers.WriteError(w, err)
return
}
helpers.WriteJSON(w, http.StatusOK, refs)
}
// GetRepositories returns all repositories in a project sorted by type
func GetRepositories(w http.ResponseWriter, r *http.Request) {
if repo := context.Get(r, "repository"); repo != nil {
@ -152,19 +163,13 @@ func RemoveRepository(w http.ResponseWriter, r *http.Request) {
var err error
softDeletion := r.URL.Query().Get("setRemoved") == "1"
if softDeletion {
err = helpers.Store(r).DeleteRepositorySoft(repository.ProjectID, repository.ID)
} else {
err = helpers.Store(r).DeleteRepository(repository.ProjectID, repository.ID)
if err == db.ErrInvalidOperation {
helpers.WriteJSON(w, http.StatusBadRequest, map[string]interface{}{
"error": "Repository is in use by one or more templates",
"inUse": true,
})
return
}
err = helpers.Store(r).DeleteRepository(repository.ProjectID, repository.ID)
if err == db.ErrInvalidOperation {
helpers.WriteJSON(w, http.StatusBadRequest, map[string]interface{}{
"error": "Repository is in use by one or more templates",
"inUse": true,
})
return
}
if err != nil {

View File

@ -161,6 +161,7 @@ func Route() *mux.Router {
projectKeyManagement.Use(projects.KeyMiddleware)
projectKeyManagement.HandleFunc("/{key_id}", projects.GetKeys).Methods("GET", "HEAD")
projectKeyManagement.HandleFunc("/{key_id}/refs", projects.GetKeyRefs).Methods("GET", "HEAD")
projectKeyManagement.HandleFunc("/{key_id}", projects.UpdateKey).Methods("PUT")
projectKeyManagement.HandleFunc("/{key_id}", projects.RemoveKey).Methods("DELETE")
@ -168,6 +169,7 @@ func Route() *mux.Router {
projectRepoManagement.Use(projects.RepositoryMiddleware)
projectRepoManagement.HandleFunc("/{repository_id}", projects.GetRepositories).Methods("GET", "HEAD")
projectRepoManagement.HandleFunc("/{repository_id}/refs", projects.GetRepositoryRefs).Methods("GET", "HEAD")
projectRepoManagement.HandleFunc("/{repository_id}", projects.UpdateRepository).Methods("PUT")
projectRepoManagement.HandleFunc("/{repository_id}", projects.RemoveRepository).Methods("DELETE")
@ -175,6 +177,7 @@ func Route() *mux.Router {
projectInventoryManagement.Use(projects.InventoryMiddleware)
projectInventoryManagement.HandleFunc("/{inventory_id}", projects.GetInventory).Methods("GET", "HEAD")
projectInventoryManagement.HandleFunc("/{inventory_id}/refs", projects.GetInventoryRefs).Methods("GET", "HEAD")
projectInventoryManagement.HandleFunc("/{inventory_id}", projects.UpdateInventory).Methods("PUT")
projectInventoryManagement.HandleFunc("/{inventory_id}", projects.RemoveInventory).Methods("DELETE")
@ -182,6 +185,7 @@ func Route() *mux.Router {
projectEnvManagement.Use(projects.EnvironmentMiddleware)
projectEnvManagement.HandleFunc("/{environment_id}", projects.GetEnvironment).Methods("GET", "HEAD")
projectEnvManagement.HandleFunc("/{environment_id}/refs", projects.GetEnvironmentRefs).Methods("GET", "HEAD")
projectEnvManagement.HandleFunc("/{environment_id}", projects.UpdateEnvironment).Methods("PUT")
projectEnvManagement.HandleFunc("/{environment_id}", projects.RemoveEnvironment).Methods("DELETE")

View File

@ -38,8 +38,6 @@ type AccessKey struct {
// You should use methods SerializeSecret to fill this field.
Secret *string `db:"secret" json:"-"`
Removed bool `db:"removed" json:"removed"`
LoginPassword LoginPassword `db:"-" json:"login_password"`
SshKey SshKey `db:"-" json:"ssh"`
PAT string `db:"-" json:"pat"`

View File

@ -11,7 +11,6 @@ type Environment struct {
ProjectID int `db:"project_id" json:"project_id"`
Password *string `db:"password" json:"password"`
JSON string `db:"json" json:"json" binding:"required"`
Removed bool `db:"removed" json:"removed"`
}
func (env *Environment) Validate() error {
@ -24,4 +23,4 @@ func (env *Environment) Validate() error {
}
return nil
}
}

View File

@ -2,8 +2,9 @@ package db
const (
InventoryStatic = "static"
InventoryFile = "file"
InventoryFile = "file"
)
// Inventory is the model of an ansible inventory file
type Inventory struct {
ID int `db:"id" json:"id"`
@ -20,8 +21,6 @@ type Inventory struct {
// static/file
Type string `db:"type" json:"type"`
Removed bool `db:"removed" json:"removed"`
}
func FillInventory(d Store, inventory *Inventory) (err error) {

View File

@ -54,6 +54,7 @@ func GetMigrations() []Migration {
{Version: "2.8.36"},
{Version: "2.8.38"},
{Version: "2.8.39"},
{Version: "2.8.40"},
}
}

View File

@ -5,6 +5,7 @@ import (
"errors"
log "github.com/Sirupsen/logrus"
"reflect"
"strings"
"time"
)
@ -39,18 +40,38 @@ type RetrieveQueryParams struct {
SortInverted bool
}
// ObjectProperties describe database entities.
type ObjectReferrerType string
const (
ObjectReferrerTemplate ObjectReferrerType = "template"
ObjectReferrerInventory ObjectReferrerType = "inventory"
ObjectReferrerRepository ObjectReferrerType = "repository"
ObjectReferrerSchedule ObjectReferrerType = "schedules"
)
type ObjectReferrer struct {
ID int `json:"id"`
Name string `json:"name"`
}
type ObjectReferrers struct {
Templates []ObjectReferrer `json:"templates"`
Inventories []ObjectReferrer `json:"inventories"`
Repositories []ObjectReferrer `json:"repositories"`
}
// ObjectProps describe database entities.
// It mainly used for NoSQL implementations (currently BoltDB) to preserve same
// data structure of different implementations and easy change it if required.
type ObjectProperties struct {
TableName string
IsGlobal bool // doesn't belong to other table, for example to project or user.
ForeignColumnSuffix string
PrimaryColumnName string
SortableColumns []string
DefaultSortingColumn string
SortInverted bool // sort from high to low object ID by default. It is useful for some NoSQL implementations.
Type reflect.Type // to which type the table bust be mapped.
type ObjectProps struct {
TableName string
Type reflect.Type // to which type the table bust be mapped.
IsGlobal bool // doesn't belong to other table, for example to project or user.
ReferringColumnSuffix string
PrimaryColumnName string
SortableColumns []string
DefaultSortingColumn string
SortInverted bool // sort from high to low object ID by default. It is useful for some NoSQL implementations.
}
var ErrNotFound = errors.New("no rows in result set")
@ -76,38 +97,38 @@ type Store interface {
IsMigrationApplied(version Migration) (bool, error)
// ApplyMigration runs executes a database migration
ApplyMigration(version Migration) error
// TryRollbackMigration attempts to rollback the database to an earlier version
// TryRollbackMigration attempts to roll back the database to an earlier version
// if a rollback exists
TryRollbackMigration(version Migration)
GetEnvironment(projectID int, environmentID int) (Environment, error)
GetEnvironmentRefs(projectID int, environmentID int) (ObjectReferrers, error)
GetEnvironments(projectID int, params RetrieveQueryParams) ([]Environment, error)
UpdateEnvironment(env Environment) error
CreateEnvironment(env Environment) (Environment, error)
DeleteEnvironment(projectID int, templateID int) error
DeleteEnvironmentSoft(projectID int, templateID int) error
GetInventory(projectID int, inventoryID int) (Inventory, error)
GetInventoryRefs(projectID int, inventoryID int) (ObjectReferrers, error)
GetInventories(projectID int, params RetrieveQueryParams) ([]Inventory, error)
UpdateInventory(inventory Inventory) error
CreateInventory(inventory Inventory) (Inventory, error)
DeleteInventory(projectID int, inventoryID int) error
DeleteInventorySoft(projectID int, inventoryID int) error
GetRepository(projectID int, repositoryID int) (Repository, error)
GetRepositoryRefs(projectID int, repositoryID int) (ObjectReferrers, error)
GetRepositories(projectID int, params RetrieveQueryParams) ([]Repository, error)
UpdateRepository(repository Repository) error
CreateRepository(repository Repository) (Repository, error)
DeleteRepository(projectID int, repositoryID int) error
DeleteRepositorySoft(projectID int, repositoryID int) error
GetAccessKey(projectID int, accessKeyID int) (AccessKey, error)
GetAccessKeyRefs(projectID int, accessKeyID int) (ObjectReferrers, error)
GetAccessKeys(projectID int, params RetrieveQueryParams) ([]AccessKey, error)
UpdateAccessKey(accessKey AccessKey) error
CreateAccessKey(accessKey AccessKey) (AccessKey, error)
DeleteAccessKey(projectID int, accessKeyID int) error
DeleteAccessKeySoft(projectID int, accessKeyID int) error
GetUsers(params RetrieveQueryParams) ([]User, error)
CreateUserWithoutPassword(user User) (User, error)
@ -179,104 +200,128 @@ type Store interface {
SetViewPositions(projectID int, viewPositions map[int]int) error
}
var AccessKeyProps = ObjectProperties{
TableName: "access_key",
SortableColumns: []string{"name", "type"},
ForeignColumnSuffix: "key_id",
PrimaryColumnName: "id",
Type: reflect.TypeOf(AccessKey{}),
DefaultSortingColumn: "name",
var AccessKeyProps = ObjectProps{
TableName: "access_key",
Type: reflect.TypeOf(AccessKey{}),
PrimaryColumnName: "id",
ReferringColumnSuffix: "key_id",
SortableColumns: []string{"name", "type"},
DefaultSortingColumn: "name",
}
var EnvironmentProps = ObjectProperties{
TableName: "project__environment",
SortableColumns: []string{"name"},
ForeignColumnSuffix: "environment_id",
PrimaryColumnName: "id",
Type: reflect.TypeOf(Environment{}),
DefaultSortingColumn: "name",
var EnvironmentProps = ObjectProps{
TableName: "project__environment",
Type: reflect.TypeOf(Environment{}),
PrimaryColumnName: "id",
ReferringColumnSuffix: "environment_id",
SortableColumns: []string{"name"},
DefaultSortingColumn: "name",
}
var InventoryProps = ObjectProperties{
TableName: "project__inventory",
SortableColumns: []string{"name"},
ForeignColumnSuffix: "inventory_id",
PrimaryColumnName: "id",
Type: reflect.TypeOf(Inventory{}),
DefaultSortingColumn: "name",
var InventoryProps = ObjectProps{
TableName: "project__inventory",
Type: reflect.TypeOf(Inventory{}),
PrimaryColumnName: "id",
ReferringColumnSuffix: "inventory_id",
SortableColumns: []string{"name"},
DefaultSortingColumn: "name",
}
var RepositoryProps = ObjectProperties{
TableName: "project__repository",
ForeignColumnSuffix: "repository_id",
PrimaryColumnName: "id",
Type: reflect.TypeOf(Repository{}),
DefaultSortingColumn: "name",
var RepositoryProps = ObjectProps{
TableName: "project__repository",
Type: reflect.TypeOf(Repository{}),
PrimaryColumnName: "id",
ReferringColumnSuffix: "repository_id",
DefaultSortingColumn: "name",
}
var TemplateProps = ObjectProperties{
var TemplateProps = ObjectProps{
TableName: "project__template",
SortableColumns: []string{"name"},
PrimaryColumnName: "id",
Type: reflect.TypeOf(Template{}),
DefaultSortingColumn: "alias",
}
var ScheduleProps = ObjectProperties{
TableName: "project__schedule",
PrimaryColumnName: "id",
Type: reflect.TypeOf(Schedule{}),
}
var ProjectUserProps = ObjectProperties{
TableName: "project__user",
PrimaryColumnName: "user_id",
Type: reflect.TypeOf(ProjectUser{}),
}
var ProjectProps = ObjectProperties{
TableName: "project",
IsGlobal: true,
PrimaryColumnName: "id",
Type: reflect.TypeOf(Project{}),
SortableColumns: []string{"name"},
DefaultSortingColumn: "name",
}
var UserProps = ObjectProperties{
var ScheduleProps = ObjectProps{
TableName: "project__schedule",
Type: reflect.TypeOf(Schedule{}),
PrimaryColumnName: "id",
}
var ProjectUserProps = ObjectProps{
TableName: "project__user",
Type: reflect.TypeOf(ProjectUser{}),
PrimaryColumnName: "user_id",
}
var ProjectProps = ObjectProps{
TableName: "project",
Type: reflect.TypeOf(Project{}),
PrimaryColumnName: "id",
DefaultSortingColumn: "name",
IsGlobal: true,
}
var UserProps = ObjectProps{
TableName: "user",
IsGlobal: true,
PrimaryColumnName: "id",
Type: reflect.TypeOf(User{}),
}
var SessionProps = ObjectProperties{
TableName: "session",
PrimaryColumnName: "id",
Type: reflect.TypeOf(Session{}),
}
var TokenProps = ObjectProperties{
TableName: "user__token",
PrimaryColumnName: "id",
Type: reflect.TypeOf(APIToken{}),
}
var TaskProps = ObjectProperties{
TableName: "task",
IsGlobal: true,
PrimaryColumnName: "id",
SortInverted: true,
Type: reflect.TypeOf(Task{}),
}
var TaskOutputProps = ObjectProperties{
var SessionProps = ObjectProps{
TableName: "session",
Type: reflect.TypeOf(Session{}),
PrimaryColumnName: "id",
}
var TokenProps = ObjectProps{
TableName: "user__token",
Type: reflect.TypeOf(APIToken{}),
PrimaryColumnName: "id",
}
var TaskProps = ObjectProps{
TableName: "task",
Type: reflect.TypeOf(Task{}),
PrimaryColumnName: "id",
IsGlobal: true,
SortInverted: true,
}
var TaskOutputProps = ObjectProps{
TableName: "task__output",
Type: reflect.TypeOf(TaskOutput{}),
}
var ViewProps = ObjectProperties{
var ViewProps = ObjectProps{
TableName: "project__view",
PrimaryColumnName: "id",
Type: reflect.TypeOf(View{}),
PrimaryColumnName: "id",
DefaultSortingColumn: "position",
}
func (p ObjectProps) GetReferringFieldsFrom(t reflect.Type) (fields []string, err error) {
n := t.NumField()
for i := 0; i < n; i++ {
if !strings.HasSuffix(t.Field(i).Tag.Get("db"), p.ReferringColumnSuffix) {
continue
}
fields = append(fields, t.Field(i).Tag.Get("db"))
}
for i := 0; i < n; i++ {
if t.Field(i).Tag != "" || t.Field(i).Type.Kind() != reflect.Struct {
continue
}
var nested []string
nested, err = p.GetReferringFieldsFrom(t.Field(i).Type)
if err != nil {
return
}
fields = append(fields, nested...)
}
return
}

View File

@ -42,8 +42,8 @@ type Template struct {
RepositoryID int `db:"repository_id" json:"repository_id"`
EnvironmentID *int `db:"environment_id" json:"environment_id"`
// Alias as described in https://github.com/ansible-semaphore/semaphore/issues/188
Alias string `db:"alias" json:"alias"`
// Name as described in https://github.com/ansible-semaphore/semaphore/issues/188
Name string `db:"name" json:"name"`
// playbook name in the form of "some_play.yml"
Playbook string `db:"playbook" json:"playbook"`
// to fit into []string
@ -74,8 +74,8 @@ type Template struct {
}
func (tpl *Template) Validate() error {
if tpl.Alias == "" {
return &ValidationError{"template alias can not be empty"}
if tpl.Name == "" {
return &ValidationError{"template name can not be empty"}
}
if tpl.Playbook == "" {

View File

@ -52,7 +52,7 @@ func (d strObjectID) ToBytes() []byte {
return []byte(d)
}
func makeBucketId(props db.ObjectProperties, ids ...int) []byte {
func makeBucketId(props db.ObjectProps, ids ...int) []byte {
n := len(ids)
id := props.TableName
@ -107,7 +107,7 @@ func (d *BoltDb) IsInitialized() (initialized bool, err error) {
return
}
func (d *BoltDb) getObject(bucketID int, props db.ObjectProperties, objectID objectID, object interface{}) (err error) {
func (d *BoltDb) getObject(bucketID int, props db.ObjectProps, objectID objectID, object interface{}) (err error) {
err = d.db.View(func(tx *bbolt.Tx) error {
b := tx.Bucket(makeBucketId(props, bucketID))
if b == nil {
@ -259,7 +259,7 @@ func marshalObject(obj interface{}) ([]byte, error) {
return json.Marshal(copyObject(obj, newType))
}
func unmarshalObjects(rawData enumerable, props db.ObjectProperties, params db.RetrieveQueryParams, filter func(interface{}) bool, objects interface{}) (err error) {
func unmarshalObjects(rawData enumerable, props db.ObjectProps, params db.RetrieveQueryParams, filter func(interface{}) bool, objects interface{}) (err error) {
objectsValue := reflect.ValueOf(objects).Elem()
objType := objectsValue.Type().Elem()
@ -317,7 +317,7 @@ func unmarshalObjects(rawData enumerable, props db.ObjectProperties, params db.R
return
}
func (d *BoltDb) getObjects(bucketID int, props db.ObjectProperties, params db.RetrieveQueryParams, filter func(interface{}) bool, objects interface{}) error {
func (d *BoltDb) getObjects(bucketID int, props db.ObjectProps, params db.RetrieveQueryParams, filter func(interface{}) bool, objects interface{}) error {
return d.db.View(func(tx *bbolt.Tx) error {
b := tx.Bucket(makeBucketId(props, bucketID))
var c enumerable
@ -330,74 +330,8 @@ func (d *BoltDb) getObjects(bucketID int, props db.ObjectProperties, params db.R
})
}
func isObjectBelongTo(props db.ObjectProperties, objID objectID, tpl interface{}) bool {
if props.ForeignColumnSuffix == "" {
return false
}
fieldName, err := getFieldNameByTagSuffix(reflect.TypeOf(tpl), "db", props.ForeignColumnSuffix)
if err != nil {
return false
}
f := reflect.ValueOf(tpl).FieldByName(fieldName)
if f.IsZero() {
return false
}
if f.Kind() == reflect.Ptr {
if f.IsNil() {
return false
}
f = f.Elem()
}
var fVal objectID
switch f.Kind() {
case reflect.Int,
reflect.Int8,
reflect.Int16,
reflect.Int32,
reflect.Int64,
reflect.Uint,
reflect.Uint8,
reflect.Uint16,
reflect.Uint32,
reflect.Uint64:
fVal = intObjectID(f.Int())
case reflect.String:
fVal = strObjectID(f.String())
}
if fVal == nil {
return false
}
return bytes.Equal(fVal.ToBytes(), objID.ToBytes())
}
// isObjectInUse checks if objID associated with any object in foreignTableProps.
func (d *BoltDb) isObjectInUse(bucketID int, objProps db.ObjectProperties, objID objectID, foreignTableProps db.ObjectProperties) (inUse bool, err error) {
templates := reflect.New(reflect.SliceOf(foreignTableProps.Type))
err = d.getObjects(bucketID, foreignTableProps, db.RetrieveQueryParams{}, func(foreignObj interface{}) bool {
return isObjectBelongTo(objProps, objID, foreignObj)
}, templates.Interface())
if err != nil {
return
}
inUse = templates.Elem().Len() > 0
return
}
func (d *BoltDb) deleteObject(bucketID int, props db.ObjectProperties, objectID objectID, tx *bbolt.Tx) error {
for _, u := range []db.ObjectProperties{db.TemplateProps, db.EnvironmentProps, db.InventoryProps, db.RepositoryProps} {
func (d *BoltDb) deleteObject(bucketID int, props db.ObjectProps, objectID objectID, tx *bbolt.Tx) error {
for _, u := range []db.ObjectProps{db.TemplateProps, db.EnvironmentProps, db.InventoryProps, db.RepositoryProps} {
inUse, err := d.isObjectInUse(bucketID, props, objectID, u)
if err != nil {
return err
@ -422,55 +356,8 @@ func (d *BoltDb) deleteObject(bucketID int, props db.ObjectProperties, objectID
return d.db.Update(fn)
}
func (d *BoltDb) deleteObjectSoft(bucketID int, props db.ObjectProperties, objectID objectID) error {
var data map[string]interface{}
// load data
err := d.db.View(func(tx *bbolt.Tx) error {
b := tx.Bucket(makeBucketId(props, bucketID))
if b == nil {
return db.ErrNotFound
}
j := b.Get(objectID.ToBytes())
if j == nil {
return db.ErrNotFound
}
return json.Unmarshal(j, &data)
})
if err != nil {
return err
}
// mark as removed if "removed" exists
if _, ok := data["removed"]; !ok {
return fmt.Errorf("removed field not exists")
}
data["removed"] = true
// store data back
res, err := json.Marshal(data)
if err != nil {
return err
}
return d.db.Update(func(tx *bbolt.Tx) error {
b := tx.Bucket(makeBucketId(props, bucketID))
if b == nil {
return db.ErrNotFound
}
return b.Put(objectID.ToBytes(), res)
})
}
// updateObject updates data for object in database.
func (d *BoltDb) updateObject(bucketID int, props db.ObjectProperties, object interface{}) error {
func (d *BoltDb) updateObject(bucketID int, props db.ObjectProps, object interface{}) error {
return d.db.Update(func(tx *bbolt.Tx) error {
b := tx.Bucket(makeBucketId(props, bucketID))
if b == nil {
@ -520,7 +407,7 @@ func (d *BoltDb) updateObject(bucketID int, props db.ObjectProperties, object in
})
}
func (d *BoltDb) createObject(bucketID int, props db.ObjectProperties, object interface{}) (interface{}, error) {
func (d *BoltDb) createObject(bucketID int, props db.ObjectProps, object interface{}) (interface{}, error) {
err := d.db.Update(func(tx *bbolt.Tx) error {
b, err := tx.CreateBucketIfNotExists(makeBucketId(props, bucketID))
@ -608,6 +495,119 @@ func (d *BoltDb) createObject(bucketID int, props db.ObjectProperties, object in
return object, err
}
func (d *BoltDb) getObjectRefs(projectID int, objectProps db.ObjectProps, objectID int) (refs db.ObjectReferrers, err error) {
refs.Templates, err = d.getObjectRefsFrom(projectID, objectProps, intObjectID(objectID), db.TemplateProps)
if err != nil {
return
}
refs.Repositories, err = d.getObjectRefsFrom(projectID, objectProps, intObjectID(objectID), db.RepositoryProps)
if err != nil {
return
}
refs.Inventories, err = d.getObjectRefsFrom(projectID, objectProps, intObjectID(objectID), db.InventoryProps)
if err != nil {
return
}
//refs.Schedules, err = d.getObjectRefsFrom(projectID, objectProps, intObjectID(objectID), db.ScheduleProps)
return
}
func (d *BoltDb) getObjectRefsFrom(projectID int, objProps db.ObjectProps, objID objectID, referringObjectProps db.ObjectProps) (referringObjs []db.ObjectReferrer, err error) {
_, err = objProps.GetReferringFieldsFrom(referringObjectProps.Type)
if err != nil {
return
}
referringObjects := reflect.New(reflect.SliceOf(referringObjectProps.Type))
err = d.getObjects(projectID, referringObjectProps, db.RetrieveQueryParams{}, func(referringObj interface{}) bool {
return isObjectReferredBy(objProps, objID, referringObj)
}, referringObjects.Interface())
if err != nil {
return
}
for i := 0; i < referringObjects.Elem().Len(); i++ {
referringObjs = append(referringObjs, db.ObjectReferrer{
ID: int(referringObjects.Elem().Index(i).FieldByName("ID").Int()),
Name: referringObjects.Elem().Index(i).FieldByName("Name").String(),
})
}
return
}
func isObjectReferredBy(props db.ObjectProps, objID objectID, referringObj interface{}) bool {
if props.ReferringColumnSuffix == "" {
return false
}
fieldName, err := getFieldNameByTagSuffix(reflect.TypeOf(referringObj), "db", props.ReferringColumnSuffix)
if err != nil {
return false
}
f := reflect.ValueOf(referringObj).FieldByName(fieldName)
if f.IsZero() {
return false
}
if f.Kind() == reflect.Ptr {
if f.IsNil() {
return false
}
f = f.Elem()
}
var fVal objectID
switch f.Kind() {
case reflect.Int,
reflect.Int8,
reflect.Int16,
reflect.Int32,
reflect.Int64,
reflect.Uint,
reflect.Uint8,
reflect.Uint16,
reflect.Uint32,
reflect.Uint64:
fVal = intObjectID(f.Int())
case reflect.String:
fVal = strObjectID(f.String())
}
if fVal == nil {
return false
}
return bytes.Equal(fVal.ToBytes(), objID.ToBytes())
}
// isObjectInUse checks if objID associated with any object in foreignTableProps.
func (d *BoltDb) isObjectInUse(bucketID int, objProps db.ObjectProps, objID objectID, referringObjectProps db.ObjectProps) (inUse bool, err error) {
referringObjects := reflect.New(reflect.SliceOf(referringObjectProps.Type))
err = d.getObjects(bucketID, referringObjectProps, db.RetrieveQueryParams{}, func(referringObj interface{}) bool {
return isObjectReferredBy(objProps, objID, referringObj)
}, referringObjects.Interface())
if err != nil {
return
}
inUse = referringObjects.Elem().Len() > 0
return
}
func CreateTestStore() BoltDb {
r := rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
fn := "/tmp/test_semaphore_db_" + strconv.Itoa(r.Int())

View File

@ -17,48 +17,48 @@ type test1 struct {
Removed bool `db:"removed"`
}
var test1props = db.ObjectProperties{
IsGlobal: true,
TableName: "test1",
PrimaryColumnName: "ID",
}
//var test1props = db.ObjectProps{
// IsGlobal: true,
// TableName: "test1",
// PrimaryColumnName: "ID",
//}
func TestDeleteObjectSoft(t *testing.T) {
store := CreateTestStore()
obj := test1{
FirstName: "Denis",
LastName: "Gukov",
}
newObj, err := store.createObject(0, test1props, obj)
if err != nil {
t.Fatal(err.Error())
}
objID := intObjectID(newObj.(test1).ID)
err = store.deleteObjectSoft(0, test1props, objID)
if err != nil {
t.Fatal(err.Error())
}
var found test1
err = store.getObject(0, test1props, objID, &found)
if err != nil {
t.Fatal(err.Error())
}
if found.ID != int(objID) ||
found.Removed != true ||
found.Password != obj.Password ||
found.LastName != obj.LastName {
t.Fatal()
}
}
//func TestDeleteObjectSoft(t *testing.T) {
// store := CreateTestStore()
//
// obj := test1{
// FirstName: "Denis",
// LastName: "Gukov",
// }
// newObj, err := store.createObject(0, test1props, obj)
//
// if err != nil {
// t.Fatal(err.Error())
// }
//
// objID := intObjectID(newObj.(test1).ID)
//
// err = store.deleteObjectSoft(0, test1props, objID)
//
// if err != nil {
// t.Fatal(err.Error())
// }
//
// var found test1
// err = store.getObject(0, test1props, objID, &found)
//
// if err != nil {
// t.Fatal(err.Error())
// }
//
// if found.ID != int(objID) ||
// found.Removed != true ||
// found.Password != obj.Password ||
// found.LastName != obj.LastName {
//
// t.Fatal()
// }
//}
func TestMarshalObject_UserWithPwd(t *testing.T) {
user := db.UserWithPwd{
@ -200,7 +200,7 @@ func TestIsObjectInUse(t *testing.T) {
}
_, err = store.CreateTemplate(db.Template{
Alias: "Test",
Name: "Test",
Playbook: "test.yml",
ProjectID: proj.ID,
InventoryID: 10,
@ -236,7 +236,7 @@ func TestIsObjectInUse_Environment(t *testing.T) {
envID := 10
_, err = store.CreateTemplate(db.Template{
Alias: "Test",
Name: "Test",
Playbook: "test.yml",
ProjectID: proj.ID,
EnvironmentID: &envID,
@ -270,7 +270,7 @@ func TestIsObjectInUse_EnvironmentNil(t *testing.T) {
}
_, err = store.CreateTemplate(db.Template{
Alias: "Test",
Name: "Test",
Playbook: "test.yml",
ProjectID: proj.ID,
EnvironmentID: nil,
@ -351,3 +351,37 @@ func TestBoltDb_CreateAPIToken(t *testing.T) {
t.Fatal()
}
}
func TestBoltDb_GetRepositoryRefs(t *testing.T) {
store := CreateTestStore()
repo1, err := store.CreateRepository(db.Repository{
Name: "repo1",
ProjectID: 1,
GitURL: "git@example.com/repo1",
GitBranch: "master",
})
if err != nil {
t.Fatal(err)
}
_, err = store.CreateTemplate(db.Template{
ProjectID: 1,
Type: db.TemplateBuild,
Name: "tpl1",
Playbook: "build.yml",
RepositoryID: repo1.ID,
})
if err != nil {
t.Fatal(err)
}
refs, err := store.GetRepositoryRefs(1, repo1.ID)
if err != nil {
t.Fatal(err)
}
if len(refs.Templates) != 1 {
t.Fatal()
}
}

View File

@ -1,89 +0,0 @@
package bolt
import (
"encoding/json"
"go.etcd.io/bbolt"
"strings"
)
type Migration_2_8_28 struct {
DB *bbolt.DB
}
func (d Migration_2_8_28) getProjectRepositories(projectID string) (map[string]map[string]interface{}, error) {
repos := make(map[string]map[string]interface{})
err := d.DB.View(func(tx *bbolt.Tx) error {
b := tx.Bucket([]byte("project__repository_" + projectID))
if b == nil {
return nil
}
return b.ForEach(func(id, body []byte) error {
r := make(map[string]interface{})
repos[string(id)] = r
return json.Unmarshal(body, &r)
})
})
return repos, err
}
func (d Migration_2_8_28) setProjectRepository(projectID string, repoID string, repo map[string]interface{}) error {
return d.DB.Update(func(tx *bbolt.Tx) error {
b, err := tx.CreateBucketIfNotExists([]byte("project__repository_" + projectID))
if err != nil {
return err
}
j, err := json.Marshal(repo)
if err != nil {
return err
}
return b.Put([]byte(repoID), j)
})
}
func (d Migration_2_8_28) Apply() (err error) {
var projectIDs []string
err = d.DB.View(func(tx *bbolt.Tx) error {
b := tx.Bucket([]byte("project"))
if b == nil {
return nil
}
return b.ForEach(func(id, _ []byte) error {
projectIDs = append(projectIDs, string(id))
return nil
})
})
if err != nil {
return
}
projectsRepositories := make(map[string]map[string]map[string]interface{})
for _, projectID := range projectIDs {
var err2 error
projectsRepositories[projectID], err2 = d.getProjectRepositories(projectID)
if err2 != nil {
return err2
}
}
for projectID, repositories := range projectsRepositories {
for repoID, repo := range repositories {
branch := "master"
url := repo["git_url"].(string)
parts := strings.Split(url, "#")
if len(parts) > 1 {
url, branch = parts[0], parts[1]
}
repo["git_url"] = url
repo["git_branch"] = branch
err = d.setProjectRepository(projectID, repoID, repo)
if err != nil {
return err
}
}
}
return nil
}

View File

@ -13,7 +13,7 @@ func TestTask_GetVersion(t *testing.T) {
build, err := store.CreateTemplate(db.Template{
ProjectID: 0,
Type: db.TemplateBuild,
Alias: "Build",
Name: "Build",
Playbook: "build.yml",
})
if err != nil {
@ -24,7 +24,7 @@ func TestTask_GetVersion(t *testing.T) {
ProjectID: 0,
Type: db.TemplateDeploy,
BuildTemplateID: &build.ID,
Alias: "Deploy",
Name: "Deploy",
Playbook: "deploy.yml",
})
if err != nil {
@ -35,7 +35,7 @@ func TestTask_GetVersion(t *testing.T) {
ProjectID: 0,
Type: db.TemplateDeploy,
BuildTemplateID: &deploy.ID,
Alias: "Deploy2",
Name: "Deploy2",
Playbook: "deploy2.yml",
})
if err != nil {

View File

@ -13,6 +13,10 @@ func (d *BoltDb) GetAccessKey(projectID int, accessKeyID int) (key db.AccessKey,
return
}
func (d *BoltDb) GetAccessKeyRefs(projectID int, accessKeyID int) (db.ObjectReferrers, error) {
return d.getObjectRefs(projectID, db.AccessKeyProps, accessKeyID)
}
func (d *BoltDb) GetAccessKeys(projectID int, params db.RetrieveQueryParams) ([]db.AccessKey, error) {
var keys []db.AccessKey
err := d.getObjects(projectID, db.AccessKeyProps, params, nil, &keys)
@ -55,7 +59,3 @@ func (d *BoltDb) CreateAccessKey(key db.AccessKey) (db.AccessKey, error) {
func (d *BoltDb) DeleteAccessKey(projectID int, accessKeyID int) error {
return d.deleteObject(projectID, db.AccessKeyProps, intObjectID(accessKeyID), nil)
}
func (d *BoltDb) DeleteAccessKeySoft(projectID int, accessKeyID int) error {
return d.deleteObjectSoft(projectID, db.AccessKeyProps, intObjectID(accessKeyID))
}

View File

@ -7,6 +7,10 @@ func (d *BoltDb) GetEnvironment(projectID int, environmentID int) (environment d
return
}
func (d *BoltDb) GetEnvironmentRefs(projectID int, environmentID int) (db.ObjectReferrers, error) {
return d.getObjectRefs(projectID, db.EnvironmentProps, environmentID)
}
func (d *BoltDb) GetEnvironments(projectID int, params db.RetrieveQueryParams) (environment []db.Environment, err error) {
err = d.getObjects(projectID, db.EnvironmentProps, params, nil, &environment)
return
@ -36,7 +40,3 @@ func (d *BoltDb) CreateEnvironment(env db.Environment) (db.Environment, error) {
func (d *BoltDb) DeleteEnvironment(projectID int, environmentID int) error {
return d.deleteObject(projectID, db.EnvironmentProps, intObjectID(environmentID), nil)
}
func (d *BoltDb) DeleteEnvironmentSoft(projectID int, environmentID int) error {
return d.deleteObjectSoft(projectID, db.EnvironmentProps, intObjectID(environmentID))
}

View File

@ -20,12 +20,12 @@ func (d *BoltDb) GetInventories(projectID int, params db.RetrieveQueryParams) (i
return
}
func (d *BoltDb) DeleteInventory(projectID int, inventoryID int) error {
return d.deleteObject(projectID, db.InventoryProps, intObjectID(inventoryID), nil)
func (d *BoltDb) GetInventoryRefs(projectID int, inventoryID int) (db.ObjectReferrers, error) {
return d.getObjectRefs(projectID, db.InventoryProps, inventoryID)
}
func (d *BoltDb) DeleteInventorySoft(projectID int, inventoryID int) error {
return d.deleteObjectSoft(projectID, db.InventoryProps, intObjectID(inventoryID))
func (d *BoltDb) DeleteInventory(projectID int, inventoryID int) error {
return d.deleteObject(projectID, db.InventoryProps, intObjectID(inventoryID), nil)
}
func (d *BoltDb) UpdateInventory(inventory db.Inventory) error {

View File

@ -33,10 +33,10 @@ func (d *BoltDb) IsMigrationApplied(migration db.Migration) (bool, error) {
return false, err
}
func (d *BoltDb) ApplyMigration(migration db.Migration) (err error) {
switch migration.Version {
func (d *BoltDb) ApplyMigration(m db.Migration) (err error) {
switch m.Version {
case "2.8.26":
err = Migration_2_8_28{DB: d.db}.Apply()
err = migration_2_8_28{migration{d.db}}.Apply()
}
if err != nil {
@ -50,18 +50,66 @@ func (d *BoltDb) ApplyMigration(migration db.Migration) (err error) {
return err
}
j, err := json.Marshal(migration)
j, err := json.Marshal(m)
if err != nil {
return err
}
return b.Put([]byte(migration.Version), j)
return b.Put([]byte(m.Version), j)
})
}
func (d *BoltDb) TryRollbackMigration(migration db.Migration) {
switch migration.Version {
func (d *BoltDb) TryRollbackMigration(m db.Migration) {
switch m.Version {
case "2.8.26":
}
}
type migration struct {
db *bbolt.DB
}
func (d migration) getProjectIDs() (projectIDs []string, err error) {
err = d.db.View(func(tx *bbolt.Tx) error {
b := tx.Bucket([]byte("project"))
if b == nil {
return nil
}
return b.ForEach(func(id, _ []byte) error {
projectIDs = append(projectIDs, string(id))
return nil
})
})
return
}
func (d migration) getObjects(projectID string, objectPrefix string) (map[string]map[string]interface{}, error) {
repos := make(map[string]map[string]interface{})
err := d.db.View(func(tx *bbolt.Tx) error {
b := tx.Bucket([]byte("project__" + objectPrefix + "_" + projectID))
if b == nil {
return nil
}
return b.ForEach(func(id, body []byte) error {
r := make(map[string]interface{})
repos[string(id)] = r
return json.Unmarshal(body, &r)
})
})
return repos, err
}
func (d migration) setObject(projectID string, objectPrefix string, objectID string, object map[string]interface{}) error {
return d.db.Update(func(tx *bbolt.Tx) error {
b, err := tx.CreateBucketIfNotExists([]byte("project__" + objectPrefix + "_" + projectID))
if err != nil {
return err
}
j, err := json.Marshal(object)
if err != nil {
return err
}
return b.Put([]byte(objectID), j)
})
}

View File

@ -0,0 +1,46 @@
package bolt
import (
"strings"
)
type migration_2_8_28 struct {
migration
}
func (d migration_2_8_28) Apply() (err error) {
projectIDs, err := d.getProjectIDs()
if err != nil {
return
}
repos := make(map[string]map[string]map[string]interface{})
for _, projectID := range projectIDs {
var err2 error
repos[projectID], err2 = d.getObjects(projectID, "repository")
if err2 != nil {
return err2
}
}
for projectID, projectRepos := range repos {
for repoID, repo := range projectRepos {
branch := "master"
url := repo["git_url"].(string)
parts := strings.Split(url, "#")
if len(parts) > 1 {
url, branch = parts[0], parts[1]
}
repo["git_url"] = url
repo["git_branch"] = branch
err = d.setObject(projectID, "repository", repoID, repo)
if err != nil {
return err
}
}
}
return nil
}

View File

@ -35,7 +35,7 @@ func TestMigration_2_8_28_Apply(t *testing.T) {
t.Fatal(err)
}
err = Migration_2_8_28{DB: store.db}.Apply()
err = migration_2_8_28{migration{store.db}}.Apply()
if err != nil {
t.Fatal(err)
}
@ -77,7 +77,7 @@ func TestMigration_2_8_28_Apply2(t *testing.T) {
t.Fatal(err)
}
err = Migration_2_8_28{DB: store.db}.Apply()
err = migration_2_8_28{migration{store.db}}.Apply()
if err != nil {
t.Fatal(err)
}

View File

@ -0,0 +1,36 @@
package bolt
type migration_2_8_40 struct {
migration
}
func (d migration_2_8_40) Apply() (err error) {
projectIDs, err := d.getProjectIDs()
if err != nil {
return
}
templates := make(map[string]map[string]map[string]interface{})
for _, projectID := range projectIDs {
var err2 error
templates[projectID], err2 = d.getObjects(projectID, "template")
if err2 != nil {
return err2
}
}
for projectID, projectTemplates := range templates {
for repoID, tpl := range projectTemplates {
tpl["name"] = tpl["alias"]
delete(tpl, "alias")
err = d.setObject(projectID, "template", repoID, tpl)
if err != nil {
return err
}
}
}
return
}

View File

@ -0,0 +1,84 @@
package bolt
import (
"encoding/json"
"go.etcd.io/bbolt"
"testing"
)
func TestMigration_2_8_40_Apply(t *testing.T) {
store := CreateTestStore()
err := store.db.Update(func(tx *bbolt.Tx) error {
b, err := tx.CreateBucketIfNotExists([]byte("project"))
if err != nil {
return err
}
err = b.Put([]byte("0000000001"), []byte("{}"))
if err != nil {
return err
}
r, err := tx.CreateBucketIfNotExists([]byte("project__template_0000000001"))
if err != nil {
return err
}
err = r.Put([]byte("0000000001"),
[]byte("{\"id\":\"1\",\"project_id\":\"1\",\"name\": \"test123\"}"))
return err
})
if err != nil {
t.Fatal(err)
}
err = migration_2_8_40{migration{store.db}}.Apply()
if err != nil {
t.Fatal(err)
}
var repo map[string]interface{}
err = store.db.View(func(tx *bbolt.Tx) error {
b := tx.Bucket([]byte("project__template_0000000001"))
str := string(b.Get([]byte("0000000001")))
return json.Unmarshal([]byte(str), &repo)
})
if err != nil {
t.Fatal(err)
}
if repo["name"].(string) != "test123" {
t.Fatal("invalid name")
}
if repo["name"] != nil {
t.Fatal("name must be deleted")
}
}
func TestMigration_2_8_40_Apply2(t *testing.T) {
store := CreateTestStore()
err := store.db.Update(func(tx *bbolt.Tx) error {
b, err := tx.CreateBucketIfNotExists([]byte("project"))
if err != nil {
return err
}
err = b.Put([]byte("0000000001"), []byte("{}"))
return err
})
if err != nil {
t.Fatal(err)
}
err = migration_2_8_28{migration{store.db}}.Apply()
if err != nil {
t.Fatal(err)
}
}

View File

@ -13,6 +13,10 @@ func (d *BoltDb) GetRepository(projectID int, repositoryID int) (repository db.R
return
}
func (d *BoltDb) GetRepositoryRefs(projectID int, repositoryID int) (db.ObjectReferrers, error) {
return d.getObjectRefs(projectID, db.RepositoryProps, repositoryID)
}
func (d *BoltDb) GetRepositories(projectID int, params db.RetrieveQueryParams) (repositories []db.Repository, err error) {
err = d.getObjects(projectID, db.RepositoryProps, params, nil, &repositories)
return
@ -38,7 +42,3 @@ func (d *BoltDb) CreateRepository(repository db.Repository) (db.Repository, erro
func (d *BoltDb) DeleteRepository(projectID int, repositoryId int) error {
return d.deleteObject(projectID, db.RepositoryProps, intObjectID(repositoryId), nil)
}
func (d *BoltDb) DeleteRepositorySoft(projectID int, repositoryId int) error {
return d.deleteObjectSoft(projectID, db.RepositoryProps, intObjectID(repositoryId))
}

View File

@ -11,7 +11,7 @@ type globalToken struct {
UserID int `db:"user_id" json:"user_id"`
}
var globalTokenObject = db.ObjectProperties{
var globalTokenObject = db.ObjectProps{
TableName: "token",
PrimaryColumnName: "id",
Type: reflect.TypeOf(globalToken{}),

View File

@ -65,7 +65,7 @@ func (d *BoltDb) getTasks(projectID int, templateID *int, params db.RetrieveQuer
}
tasksWithTpl[i] = db.TaskWithTpl{Task: task}
tasksWithTpl[i].TemplatePlaybook = tpl.Playbook
tasksWithTpl[i].TemplateAlias = tpl.Alias
tasksWithTpl[i].TemplateAlias = tpl.Name
tasksWithTpl[i].TemplateType = tpl.Type
if task.UserID != nil {
usr, ok := users[*task.UserID]

View File

@ -11,6 +11,7 @@ import (
"github.com/gobuffalo/packr"
_ "github.com/lib/pq"
"github.com/masterminds/squirrel"
"reflect"
"regexp"
"strconv"
"strings"
@ -182,7 +183,7 @@ func createDb() error {
return nil
}
func (d *SqlDb) getObject(projectID int, props db.ObjectProperties, objectID int, object interface{}) (err error) {
func (d *SqlDb) getObject(projectID int, props db.ObjectProps, objectID int, object interface{}) (err error) {
q := squirrel.Select("*").
From(props.TableName).
Where("id=?", objectID)
@ -208,7 +209,7 @@ func (d *SqlDb) getObject(projectID int, props db.ObjectProperties, objectID int
return
}
func (d *SqlDb) getObjects(projectID int, props db.ObjectProperties, params db.RetrieveQueryParams, objects interface{}) (err error) {
func (d *SqlDb) getObjects(projectID int, props db.ObjectProps, params db.RetrieveQueryParams, objects interface{}) (err error) {
q := squirrel.Select("*").
From(props.TableName+" pe").
Where("pe.project_id=?", projectID)
@ -238,7 +239,7 @@ func (d *SqlDb) getObjects(projectID int, props db.ObjectProperties, params db.R
return
}
func (d *SqlDb) deleteObject(projectID int, props db.ObjectProperties, objectID int) error {
func (d *SqlDb) deleteObject(projectID int, props db.ObjectProps, objectID int) error {
return validateMutationResult(
d.exec(
"delete from "+props.TableName+" where project_id=? and id=?",
@ -246,14 +247,6 @@ func (d *SqlDb) deleteObject(projectID int, props db.ObjectProperties, objectID
objectID))
}
func (d *SqlDb) deleteObjectSoft(projectID int, props db.ObjectProperties, objectID int) error {
return validateMutationResult(
d.exec(
"update "+props.TableName+" set removed=1 where project_id=? and id=?",
projectID,
objectID))
}
func (d *SqlDb) Close() error {
return d.sql.Db.Close()
}
@ -338,6 +331,118 @@ func getSqlForTable(tableName string, p db.RetrieveQueryParams) (string, []inter
return q.ToSql()
}
func (d *SqlDb) getObjectRefs(projectID int, objectProps db.ObjectProps, objectID int) (refs db.ObjectReferrers, err error) {
refs.Templates, err = d.getObjectRefsFrom(projectID, objectProps, objectID, db.TemplateProps)
if err != nil {
return
}
refs.Repositories, err = d.getObjectRefsFrom(projectID, objectProps, objectID, db.RepositoryProps)
if err != nil {
return
}
refs.Inventories, err = d.getObjectRefsFrom(projectID, objectProps, objectID, db.InventoryProps)
if err != nil {
return
}
templates, err := d.getObjectRefsFrom(projectID, objectProps, objectID, db.ScheduleProps)
if err != nil {
return
}
for _, st := range templates {
exists := false
for _, tpl := range refs.Templates {
if tpl.ID == st.ID {
exists = true
break
}
}
if exists {
continue
}
refs.Templates = append(refs.Templates, st)
}
return
}
func (d *SqlDb) getObjectRefCount(projectID int, objectProps db.ObjectProps, objectID int) (int, error) {
return 0, nil
}
func (d *SqlDb) getObjectRefsFrom(
projectID int,
objectProps db.ObjectProps,
objectID int,
referringObjectProps db.ObjectProps,
) (referringObjs []db.ObjectReferrer, err error) {
referringObjs = make([]db.ObjectReferrer, 0)
fields, err := objectProps.GetReferringFieldsFrom(referringObjectProps.Type)
cond := ""
vals := []interface{}{projectID}
for _, f := range fields {
if cond != "" {
cond += ", "
}
cond += f + " = ?"
vals = append(vals, objectID)
}
if cond == "" {
return
}
var referringObjects reflect.Value
if referringObjectProps.Type == db.ScheduleProps.Type {
var referringSchedules []db.Schedule
_, err = d.selectAll(&referringSchedules, "select template_id id from project__schedule where project_id = ? and "+cond, vals...)
if err != nil {
return
}
if len(referringSchedules) == 0 {
return
}
var ids []string
for _, schedule := range referringSchedules {
ids = append(ids, strconv.Itoa(schedule.ID))
}
referringObjects = reflect.New(reflect.SliceOf(db.TemplateProps.Type))
_, err = d.selectAll(referringObjects.Interface(),
"select id, name from project__template where id in ("+strings.Join(ids, ",")+")")
} else {
referringObjects = reflect.New(reflect.SliceOf(referringObjectProps.Type))
_, err = d.selectAll(
referringObjects.Interface(),
"select id, name from "+referringObjectProps.TableName+" where project_id = ? and "+cond,
vals...)
}
if err != nil {
return
}
for i := 0; i < referringObjects.Elem().Len(); i++ {
id := int(referringObjects.Elem().Index(i).FieldByName("ID").Int())
name := referringObjects.Elem().Index(i).FieldByName("Name").String()
referringObjs = append(referringObjs, db.ObjectReferrer{ID: id, Name: name})
}
return
}
func (d *SqlDb) Sql() *gorp.DbMap {
return d.sql
}

View File

@ -15,6 +15,10 @@ func (d *SqlDb) GetAccessKey(projectID int, accessKeyID int) (key db.AccessKey,
return
}
func (d *SqlDb) GetAccessKeyRefs(projectID int, keyID int) (db.ObjectReferrers, error) {
return d.getObjectRefs(projectID, db.AccessKeyProps, keyID)
}
func (d *SqlDb) GetAccessKeys(projectID int, params db.RetrieveQueryParams) ([]db.AccessKey, error) {
var keys []db.AccessKey
err := d.getObjects(projectID, db.AccessKeyProps, params, &keys)
@ -83,7 +87,3 @@ func (d *SqlDb) CreateAccessKey(key db.AccessKey) (newKey db.AccessKey, err erro
func (d *SqlDb) DeleteAccessKey(projectID int, accessKeyID int) error {
return d.deleteObject(projectID, db.AccessKeyProps, accessKeyID)
}
func (d *SqlDb) DeleteAccessKeySoft(projectID int, accessKeyID int) error {
return d.deleteObjectSoft(projectID, db.AccessKeyProps, accessKeyID)
}

View File

@ -1,6 +1,8 @@
package sql
import "github.com/ansible-semaphore/semaphore/db"
import (
"github.com/ansible-semaphore/semaphore/db"
)
func (d *SqlDb) GetEnvironment(projectID int, environmentID int) (db.Environment, error) {
var environment db.Environment
@ -8,6 +10,10 @@ func (d *SqlDb) GetEnvironment(projectID int, environmentID int) (db.Environment
return environment, err
}
func (d *SqlDb) GetEnvironmentRefs(projectID int, environmentID int) (db.ObjectReferrers, error) {
return d.getObjectRefs(projectID, db.EnvironmentProps, environmentID)
}
func (d *SqlDb) GetEnvironments(projectID int, params db.RetrieveQueryParams) ([]db.Environment, error) {
var environment []db.Environment
err := d.getObjects(projectID, db.EnvironmentProps, params, &environment)
@ -56,7 +62,3 @@ func (d *SqlDb) CreateEnvironment(env db.Environment) (newEnv db.Environment, er
func (d *SqlDb) DeleteEnvironment(projectID int, environmentID int) error {
return d.deleteObject(projectID, db.EnvironmentProps, environmentID)
}
func (d *SqlDb) DeleteEnvironmentSoft(projectID int, environmentID int) error {
return d.deleteObjectSoft(projectID, db.EnvironmentProps, environmentID)
}

View File

@ -18,12 +18,12 @@ func (d *SqlDb) GetInventories(projectID int, params db.RetrieveQueryParams) ([]
return inventories, err
}
func (d *SqlDb) DeleteInventory(projectID int, inventoryID int) error {
return d.deleteObject(projectID, db.InventoryProps, inventoryID)
func (d *SqlDb) GetInventoryRefs(projectID int, inventoryID int) (db.ObjectReferrers, error) {
return d.getObjectRefs(projectID, db.InventoryProps, inventoryID)
}
func (d *SqlDb) DeleteInventorySoft(projectID int, inventoryID int) error {
return d.deleteObjectSoft(projectID, db.InventoryProps, inventoryID)
func (d *SqlDb) DeleteInventory(projectID int, inventoryID int) error {
return d.deleteObject(projectID, db.InventoryProps, inventoryID)
}
func (d *SqlDb) UpdateInventory(inventory db.Inventory) error {
@ -58,6 +58,3 @@ func (d *SqlDb) CreateInventory(inventory db.Inventory) (newInventory db.Invento
newInventory.ID = insertID
return
}

View File

@ -18,6 +18,7 @@ var (
longtextRE = regexp.MustCompile(`(?i)\blongtext\b`)
ifExistsRE = regexp.MustCompile(`(?i)\bif exists\b`)
dropForeignKey = regexp.MustCompile(`(?i)\bdrop foreign key\b`)
changeRE = regexp.MustCompile(`^alter table \x60(\w+)\x60 change \x60(\w+)\x60 \x60(\w+)\x60 ([\w\(\)]+)( not null)?$`)
)
// getVersionPath is the humanoid version with the file format appended
@ -38,6 +39,9 @@ func getVersionSQL(path string) (queries []string) {
panic(err)
}
queries = strings.Split(strings.ReplaceAll(sql, ";\r\n", ";\n"), ";\n")
for i := range queries {
queries[i] = strings.Trim(queries[i], "\n\t ")
}
return
}
@ -49,12 +53,41 @@ func (d *SqlDb) prepareMigration(query string) string {
query = autoIncrementRE.ReplaceAllString(query, "auto_increment")
query = ifExistsRE.ReplaceAllString(query, "")
case gorp.PostgresDialect:
query = serialRE.ReplaceAllString(query, "serial primary key")
query = identifierQuoteRE.ReplaceAllString(query, "\"")
m := changeRE.FindStringSubmatch(query)
var queries []string
if m != nil {
tableName := m[1]
oldColumnName := m[2]
newColumnName := m[3]
columnType := m[4]
columnNotNull := m[5] != ""
queries = append(queries,
"alter table `"+tableName+"` alter column `"+oldColumnName+"` type "+columnType)
if columnNotNull {
queries = append(queries,
"alter table `"+tableName+"` alter column `"+oldColumnName+"` set not null")
} else {
queries = append(queries,
"alter table `"+tableName+"` alter column `"+oldColumnName+"` drop not null")
}
if oldColumnName != newColumnName {
queries = append(queries,
"alter table `"+tableName+"` rename column `"+oldColumnName+"` to `"+newColumnName+"`")
}
query = strings.Join(queries, "; ")
}
query = dateTimeTypeRE.ReplaceAllString(query, "timestamp")
query = tinyintRE.ReplaceAllString(query, "smallint")
query = longtextRE.ReplaceAllString(query, "text")
query = serialRE.ReplaceAllString(query, "serial primary key")
query = dropForeignKey.ReplaceAllString(query, "drop constraint")
query = identifierQuoteRE.ReplaceAllString(query, "\"")
}
return query
}
@ -130,7 +163,7 @@ func (d *SqlDb) ApplyMigration(migration db.Migration) error {
switch migration.Version {
case "2.8.26":
err = Migration_2_8_26{DB: d}.Apply(tx)
err = migration_2_8_26{db: d}.Apply(tx)
}
if err != nil {

View File

@ -5,12 +5,12 @@ import (
"strings"
)
type Migration_2_8_26 struct {
DB *SqlDb
type migration_2_8_26 struct {
db *SqlDb
}
func (m Migration_2_8_26) Apply(tx *gorp.Transaction) error {
rows, err := tx.Query(m.DB.PrepareQuery("SELECT id, git_url FROM project__repository"))
func (m migration_2_8_26) Apply(tx *gorp.Transaction) error {
rows, err := tx.Query(m.db.PrepareQuery("SELECT id, git_url FROM project__repository"))
if err != nil {
return err
}
@ -39,7 +39,7 @@ func (m Migration_2_8_26) Apply(tx *gorp.Transaction) error {
if len(parts) > 1 {
url, branch = parts[0], parts[1]
}
q := m.DB.PrepareQuery("UPDATE project__repository " +
q := m.db.PrepareQuery("UPDATE project__repository " +
"SET git_url = ?, git_branch = ? " +
"WHERE id = ?")
_, err = tx.Exec(q, url, branch, id)

View File

@ -1,3 +1,3 @@
delete from project__template where removed = true;
delete from `project__template` where `removed` = true;
alter table `project__template` drop column `removed`;

View File

@ -0,0 +1,11 @@
alter table `project` change `alert_chat` `alert_chat` varchar(30);
alter table `project__template` change `alias` `name` varchar(100) not null;
alter table `project__inventory` drop column `removed`;
alter table `project__environment` drop column `removed`;
alter table `access_key` drop column `removed`;
alter table `project__repository` drop column `removed`;

View File

@ -18,6 +18,10 @@ func (d *SqlDb) GetRepository(projectID int, repositoryID int) (db.Repository, e
return repository, err
}
func (d *SqlDb) GetRepositoryRefs(projectID int, repositoryID int) (db.ObjectReferrers, error) {
return d.getObjectRefs(projectID, db.RepositoryProps, repositoryID)
}
func (d *SqlDb) GetRepositories(projectID int, params db.RetrieveQueryParams) (repositories []db.Repository, err error) {
q := squirrel.Select("*").
From("project__repository pr")
@ -97,7 +101,3 @@ func (d *SqlDb) CreateRepository(repository db.Repository) (newRepo db.Repositor
func (d *SqlDb) DeleteRepository(projectID int, repositoryId int) error {
return d.deleteObject(projectID, db.RepositoryProps, repositoryId)
}
func (d *SqlDb) DeleteRepositorySoft(projectID int, repositoryId int) error {
return d.deleteObjectSoft(projectID, db.RepositoryProps, repositoryId)
}

View File

@ -35,7 +35,7 @@ func (d *SqlDb) getTasks(projectID int, templateID *int, params db.RetrieveQuery
fields := "task.*"
fields += ", tpl.playbook as tpl_playbook" +
", `user`.name as user_name" +
", tpl.alias as tpl_alias" +
", tpl.name as tpl_alias" +
", tpl.type as tpl_type"
q := squirrel.Select(fields).

View File

@ -16,14 +16,14 @@ 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, allow_override_args_in_task, description, vault_key_id, `type`, start_version,"+
"name, 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,
template.InventoryID,
template.RepositoryID,
template.EnvironmentID,
template.Alias,
template.Name,
template.Playbook,
template.Arguments,
template.AllowOverrideArgsInTask,
@ -63,7 +63,7 @@ func (d *SqlDb) UpdateTemplate(template db.Template) error {
"inventory_id=?, "+
"repository_id=?, "+
"environment_id=?, "+
"alias=?, "+
"name=?, "+
"playbook=?, "+
"arguments=?, "+
"allow_override_args_in_task=?, "+
@ -79,7 +79,7 @@ func (d *SqlDb) UpdateTemplate(template db.Template) error {
template.InventoryID,
template.RepositoryID,
template.EnvironmentID,
template.Alias,
template.Name,
template.Playbook,
template.Arguments,
template.AllowOverrideArgsInTask,
@ -103,7 +103,7 @@ func (d *SqlDb) GetTemplates(projectID int, filter db.TemplateFilter, params db.
"pt.inventory_id",
"pt.repository_id",
"pt.environment_id",
"pt.alias",
"pt.name",
"pt.playbook",
"pt.arguments",
"pt.allow_override_args_in_task",
@ -129,7 +129,7 @@ func (d *SqlDb) GetTemplates(projectID int, filter db.TemplateFilter, params db.
}
switch params.SortBy {
case "alias", "playbook":
case "name", "playbook":
q = q.Where("pt.project_id=?", projectID).
OrderBy("pt." + params.SortBy + " " + order)
case "inventory":
@ -146,7 +146,7 @@ func (d *SqlDb) GetTemplates(projectID int, filter db.TemplateFilter, params db.
OrderBy("pr.name " + order)
default:
q = q.Where("pt.project_id=?", projectID).
OrderBy("pt.alias " + order)
OrderBy("pt.name " + order)
}
query, args, err := q.ToSql()

View File

@ -10,17 +10,17 @@ import (
"github.com/ansible-semaphore/semaphore/util"
)
const emailTemplate = `Subject: Task '{{ .Alias }}' failed
const emailTemplate = `Subject: Task '{{ .Name }}' failed
Task {{ .TaskID }} with template '{{ .Alias }}' has failed!
Task {{ .TaskID }} with template '{{ .Name }}' has failed!
Task log: <a href='{{ .TaskURL }}'>{{ .TaskURL }}</a>`
const telegramTemplate = `{"chat_id": "{{ .ChatID }}","parse_mode":"HTML","text":"<code>{{ .Alias }}</code>\n#{{ .TaskID }} <b>{{ .TaskResult }}</b> <code>{{ .TaskVersion }}</code> {{ .TaskDescription }}\nby {{ .Author }}\n{{ .TaskURL }}"}`
const telegramTemplate = `{"chat_id": "{{ .ChatID }}","parse_mode":"HTML","text":"<code>{{ .Name }}</code>\n#{{ .TaskID }} <b>{{ .TaskResult }}</b> <code>{{ .TaskVersion }}</code> {{ .TaskDescription }}\nby {{ .Author }}\n{{ .TaskURL }}"}`
// Alert represents an alert that will be templated and sent to the appropriate service
type Alert struct {
TaskID string
Alias string
Name string
TaskURL string
ChatID string
TaskResult string
@ -39,7 +39,7 @@ func (t *TaskRunner) sendMailAlert() {
var mailBuffer bytes.Buffer
alert := Alert{
TaskID: strconv.Itoa(t.task.ID),
Alias: t.template.Alias,
Name: t.template.Name,
TaskURL: util.Config.WebHost + "/project/" + strconv.Itoa(t.template.ProjectID),
}
tpl := template.New("mail body template")
@ -103,7 +103,7 @@ func (t *TaskRunner) sendTelegramAlert() {
alert := Alert{
TaskID: strconv.Itoa(t.task.ID),
Alias: t.template.Alias,
Name: t.template.Name,
TaskURL: util.Config.WebHost + "/project/" + strconv.Itoa(t.template.ProjectID) + "/templates/" + strconv.Itoa(t.template.ID) + "?t=" + strconv.Itoa(t.task.ID),
ChatID: chatID,
TaskResult: strings.ToUpper(t.task.Status),

View File

@ -18,14 +18,25 @@ type logRecord struct {
}
type TaskPool struct {
queue []*TaskRunner
register chan *TaskRunner
activeProj map[int]*TaskRunner
activeNodes map[string]*TaskRunner
running int
// queue contains list of tasks in status TaskWaitingStatus.
queue []*TaskRunner
// register channel used to put tasks to queue.
register chan *TaskRunner
activeProj map[int]*TaskRunner
activeNodes map[string]*TaskRunner
// running is a number of task with status TaskRunningStatus.
running int
// runningTasks contains tasks with status TaskRunningStatus.
runningTasks map[int]*TaskRunner
logger chan logRecord
store db.Store
// logger channel used to putting log records to database.
logger chan logRecord
store db.Store
}
type resourceLock struct {

View File

@ -147,7 +147,7 @@ func (t *TaskRunner) destroyKeys() {
func (t *TaskRunner) createTaskEvent() {
objType := db.EventTask
desc := "Task ID " + strconv.Itoa(t.task.ID) + " (" + t.template.Alias + ")" + " finished - " + strings.ToUpper(t.task.Status)
desc := "Task ID " + strconv.Itoa(t.task.ID) + " (" + t.template.Name + ")" + " finished - " + strings.ToUpper(t.task.Status)
_, err := t.pool.store.CreateEvent(db.Event{
UserID: t.task.UserID,
@ -190,7 +190,7 @@ func (t *TaskRunner) prepareRun() {
}
objType := db.EventTask
desc := "Task ID " + strconv.Itoa(t.task.ID) + " (" + t.template.Alias + ")" + " is preparing"
desc := "Task ID " + strconv.Itoa(t.task.ID) + " (" + t.template.Name + ")" + " is preparing"
_, err = t.pool.store.CreateEvent(db.Event{
UserID: t.task.UserID,
ProjectID: &t.task.ProjectID,
@ -204,7 +204,7 @@ func (t *TaskRunner) prepareRun() {
panic(err)
}
t.Log("Prepare TaskRunner with template: " + t.template.Alias + "\n")
t.Log("Prepare TaskRunner with template: " + t.template.Name + "\n")
t.updateStatus()
@ -287,7 +287,7 @@ func (t *TaskRunner) run() {
t.setStatus(db.TaskRunningStatus)
objType := db.EventTask
desc := "Task ID " + strconv.Itoa(t.task.ID) + " (" + t.template.Alias + ")" + " is running"
desc := "Task ID " + strconv.Itoa(t.task.ID) + " (" + t.template.Name + ")" + " is running"
_, err := t.pool.store.CreateEvent(db.Event{
UserID: t.task.UserID,
@ -303,7 +303,7 @@ func (t *TaskRunner) run() {
}
t.Log("Started: " + strconv.Itoa(t.task.ID))
t.Log("Run TaskRunner with template: " + t.template.Alias + "\n")
t.Log("Run TaskRunner with template: " + t.template.Name + "\n")
// TODO: ?????
if t.task.Status == db.TaskStoppingStatus {

View File

@ -65,7 +65,7 @@ func TestPopulateDetails(t *testing.T) {
}
tpl, err := store.CreateTemplate(db.Template{
Alias: "Test",
Name: "Test",
Playbook: "test.yml",
ProjectID: proj.ID,
RepositoryID: repo.ID,

View File

@ -51,7 +51,7 @@
class="breadcrumbs__item breadcrumbs__item--link"
:to="`/project/${projectId}/templates/${template ? template.id : null}`"
@click="taskLogDialog = false"
>{{ template ? template.alias : null }}
>{{ template ? template.name : null }}
</router-link>
<v-icon>mdi-chevron-right</v-icon>
<span class="breadcrumbs__item">Task #{{ task ? task.id : null }}</span>

View File

@ -41,6 +41,7 @@ Can use used in tandem with ItemFormBase.js. See KeyForm.vue for example.
color="blue darken-1"
text
@click="needSave = true"
v-if="saveButtonText != null"
>
{{ saveButtonText }}
</v-btn>

View File

@ -2,12 +2,15 @@ import axios from 'axios';
import EventBus from '@/event-bus';
import EditDialog from '@/components/EditDialog.vue';
import YesNoDialog from '@/components/YesNoDialog.vue';
import ObjectRefsView from '@/components/ObjectRefsView.vue';
import { getErrorMessage } from '@/lib/error';
export default {
components: {
YesNoDialog,
EditDialog,
ObjectRefsView,
},
props: {
@ -19,9 +22,13 @@ export default {
return {
headers: this.getHeaders(),
items: null,
itemId: null,
editDialog: null,
deleteItemDialog: null,
itemRefs: null,
itemRefsDialog: null,
};
},
@ -32,7 +39,8 @@ export default {
methods: {
// eslint-disable-next-line no-empty-function
async beforeLoadItems() { },
async beforeLoadItems() {
},
getSingleItemUrl() {
throw new Error('Not implemented');
@ -54,8 +62,23 @@ export default {
await this.loadItems();
},
askDeleteItem(itemId) {
async askDeleteItem(itemId) {
this.itemId = itemId;
this.itemRefs = (await axios({
method: 'get',
url: `${this.getSingleItemUrl()}/refs`,
responseType: 'json',
})).data;
if (this.itemRefs.templates.length > 0
|| this.itemRefs.repositories.length > 0
|| this.itemRefs.inventories.length > 0
|| this.itemRefs.schedules.length > 0) {
this.itemRefsDialog = true;
return;
}
this.deleteItemDialog = true;
},

View File

@ -0,0 +1,33 @@
<template>
<div>
<div>{{ title }}</div>
<div v-if="objectRefs.templates.length > 0">
<div>Templates</div>
<router-link
v-for="t in objectRefs.templates"
:key="t.id"
:to="`/project/${projectId}/templates/${t.id}`">{{ t.name }}
</router-link>
</div>
<div v-if="objectRefs.inventories.length > 0">
<div>Inventories</div>
<a v-for="referrer in objectRefs.inventories" :key="referrer.id">{{ referrer.name }}</a>
</div>
<div v-if="objectRefs.repositories.length > 0">
<div>Repositories</div>
<a v-for="referrer in objectRefs.repositories" :key="referrer.id">{{ referrer.name }}</a>
</div>
</div>
</template>
<script>
export default {
props: {
objectRefs: Object,
projectId: Number,
title: String,
},
};
</script>

View File

@ -7,7 +7,7 @@
>
<template v-slot:title={}>
<v-icon class="mr-4">{{ TEMPLATE_TYPE_ICONS[template.type] }}</v-icon>
<span class="breadcrumbs__item">{{ template.alias }}</span>
<span class="breadcrumbs__item">{{ template.name }}</span>
<v-icon>mdi-chevron-right</v-icon>
<span class="breadcrumbs__item">New Task</span>
</template>

View File

@ -98,7 +98,7 @@
label="Build Template"
:items="buildTemplates"
item-value="id"
item-text="alias"
item-text="name"
:rules="[v => !!v || 'Build Template is required']"
required
:disabled="formSaving"
@ -117,9 +117,9 @@
</v-card>
<v-text-field
v-model="item.alias"
label="Playbook Alias"
:rules="[v => !!v || 'Playbook Alias is required']"
v-model="item.name"
label="Playbook Name"
:rules="[v => !!v || 'Playbook Name is required']"
required
:disabled="formSaving"
></v-text-field>

View File

@ -19,6 +19,17 @@
</template>
</EditDialog>
<EditDialog
v-model="itemRefsDialog"
title="Environment in use"
:max-width="500"
hide-buttons
>
<template>
<ObjectRefsView :object-refs="itemRefs" />
</template>
</EditDialog>
<YesNoDialog
title="Delete environment"
text="Are you really want to delete this environment?"

View File

@ -18,6 +18,20 @@
</template>
</EditDialog>
<EditDialog
v-model="itemRefsDialog"
title="Can't delete the repository"
:max-width="500"
>
<template v-slot:form="{}">
<ObjectRefsView
title="The repository used by following resources:"
:object-refs="itemRefs"
:project-id="projectId"
/>
</template>
</EditDialog>
<YesNoDialog
title="Delete repository"
text="Are you really want to delete this repository?"

View File

@ -63,7 +63,7 @@
: `/project/${projectId}/templates/`"
>Task Templates</router-link>
<v-icon>mdi-chevron-right</v-icon>
<span class="breadcrumbs__item">{{ item.alias }}</span>
<span class="breadcrumbs__item">{{ item.name }}</span>
</v-toolbar-title>
<v-spacer></v-spacer>
@ -277,7 +277,7 @@ export default {
EventBus.$emit('i-snackbar', {
color: 'success',
text: `Template "${this.item.alias}" deleted`,
text: `Template "${this.item.name}" deleted`,
});
await this.$router.push({

View File

@ -132,7 +132,7 @@
opacity: viewItemsLoading ? 0.3 : 1,
}"
>
<template v-slot:item.alias="{ item }">
<template v-slot:item.name="{ item }">
<v-icon class="mr-3" small>
{{ TEMPLATE_TYPE_ICONS[item.type] }}
</v-icon>
@ -140,7 +140,7 @@
:to="viewId
? `/project/${projectId}/views/${viewId}/templates/${item.id}`
: `/project/${projectId}/templates/${item.id}`"
>{{ item.alias }}</router-link>
>{{ item.name }}</router-link>
</template>
<template v-slot:item.version="{ item }">
@ -300,7 +300,7 @@ export default {
if (this.itemId == null || this.itemId === 'new') {
return '';
}
return this.items.find((x) => x.id === this.itemId).alias;
return this.items.find((x) => x.id === this.itemId).name;
},
isLoaded() {
@ -403,8 +403,8 @@ export default {
getHeaders() {
return [
{
text: 'Alias',
value: 'alias',
text: 'Name',
value: 'name',
},
{
text: 'Version',