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 curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.5/install.sh | bash
export NVM_DIR="$HOME/.nvm" export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" [ -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 # Each step uses the same `$BASH_ENV`, so need to modify it
echo 'export NVM_DIR="$HOME/.nvm"' >> $BASH_ENV echo 'export NVM_DIR="$HOME/.nvm"' >> $BASH_ENV
echo "[ -s \"$NVM_DIR/nvm.sh\" ] && . \"$NVM_DIR/nvm.sh\"" >> $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": case "template":
res, err := store.Sql().Exec( res, err := store.Sql().Exec(
"insert into project__template "+ "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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", "values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
userProject.ID, inventoryID, repoID, environmentID, "Test-"+uid, "test-playbook.yml", "", false, "Hello, World!", view.ID) userProject.ID, inventoryID, repoID, environmentID, "Test-"+uid, "test-playbook.yml", "", false, "Hello, World!", view.ID)
printError(err) printError(err)

3
.gitignore vendored
View File

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

View File

@ -326,7 +326,7 @@ definitions:
view_id: view_id:
type: integer type: integer
minimum: 1 minimum: 1
alias: name:
type: string type: string
example: Test example: Test
playbook: playbook:
@ -361,7 +361,7 @@ definitions:
view_id: view_id:
type: integer type: integer
minimum: 1 minimum: 1
alias: name:
type: string type: string
example: Test example: Test
playbook: playbook:
@ -1221,7 +1221,7 @@ paths:
required: true required: true
type: string type: string
description: sorting name description: sorting name
enum: [alias, playbook, ssh_key, inventory, environment, repository] enum: [name, playbook, ssh_key, inventory, environment, repository]
- name: order - name: order
in: query in: query
required: true 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 // GetEnvironment retrieves sorted environments from the database
func GetEnvironment(w http.ResponseWriter, r *http.Request) { 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 { if env.ID != oldEnv.ID {
helpers.WriteJSON(w, http.StatusBadRequest, map[string]string{ helpers.WriteJSON(w, http.StatusBadRequest, map[string]string{
"error": "Environment ID in body and URL must be the same", "error": "Environment ID in body and URL must be the same",
}) })
return return
} }
@ -109,7 +120,7 @@ func AddEnvironment(w http.ResponseWriter, r *http.Request) {
desc := "Environment " + newEnv.Name + " created" desc := "Environment " + newEnv.Name + " created"
_, err = helpers.Store(r).CreateEvent(db.Event{ _, err = helpers.Store(r).CreateEvent(db.Event{
UserID: &user.ID, UserID: &user.ID,
ProjectID: &newEnv.ID, ProjectID: &newEnv.ID,
ObjectType: &objType, ObjectType: &objType,
ObjectID: &newEnv.ID, ObjectID: &newEnv.ID,
@ -129,19 +140,13 @@ func RemoveEnvironment(w http.ResponseWriter, r *http.Request) {
var err error var err error
softDeletion := r.URL.Query().Get("setRemoved") == "1" err = helpers.Store(r).DeleteEnvironment(env.ProjectID, env.ID)
if err == db.ErrInvalidOperation {
if softDeletion { helpers.WriteJSON(w, http.StatusBadRequest, map[string]interface{}{
err = helpers.Store(r).DeleteEnvironmentSoft(env.ProjectID, env.ID) "error": "Environment is in use by one or more templates",
} else { "inUse": true,
err = helpers.Store(r).DeleteEnvironment(env.ProjectID, env.ID) })
if err == db.ErrInvalidOperation { return
helpers.WriteJSON(w, http.StatusBadRequest, map[string]interface{}{
"error": "Environment is in use by one or more templates",
"inUse": true,
})
return
}
} }
if err != nil { 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 // GetInventory returns an inventory from the database
func GetInventory(w http.ResponseWriter, r *http.Request) { func GetInventory(w http.ResponseWriter, r *http.Request) {
if inventory := context.Get(r, "inventory"); inventory != nil { 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) inventory := context.Get(r, "inventory").(db.Inventory)
var err error var err error
softDeletion := r.URL.Query().Get("setRemoved") == "1" err = helpers.Store(r).DeleteInventory(inventory.ProjectID, inventory.ID)
if err == db.ErrInvalidOperation {
if softDeletion { helpers.WriteJSON(w, http.StatusBadRequest, map[string]interface{}{
err = helpers.Store(r).DeleteInventorySoft(inventory.ProjectID, inventory.ID) "error": "Inventory is in use by one or more templates",
} else { "inUse": true,
err = helpers.Store(r).DeleteInventory(inventory.ProjectID, inventory.ID) })
if err == db.ErrInvalidOperation { return
helpers.WriteJSON(w, http.StatusBadRequest, map[string]interface{}{
"error": "Inventory is in use by one or more templates",
"inUse": true,
})
return
}
} }
if err != nil { 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 // GetKeys retrieves sorted keys from the database
func GetKeys(w http.ResponseWriter, r *http.Request) { func GetKeys(w http.ResponseWriter, r *http.Request) {
if key := context.Get(r, "accessKey"); key != nil { if key := context.Get(r, "accessKey"); key != nil {
@ -161,19 +172,13 @@ func RemoveKey(w http.ResponseWriter, r *http.Request) {
var err error var err error
softDeletion := r.URL.Query().Get("setRemoved") == "1" err = helpers.Store(r).DeleteAccessKey(*key.ProjectID, key.ID)
if err == db.ErrInvalidOperation {
if softDeletion { helpers.WriteJSON(w, http.StatusBadRequest, map[string]interface{}{
err = helpers.Store(r).DeleteAccessKeySoft(*key.ProjectID, key.ID) "error": "Access Key is in use by one or more templates",
} else { "inUse": true,
err = helpers.Store(r).DeleteAccessKey(*key.ProjectID, key.ID) })
if err == db.ErrInvalidOperation { return
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 { 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 // GetRepositories returns all repositories in a project sorted by type
func GetRepositories(w http.ResponseWriter, r *http.Request) { func GetRepositories(w http.ResponseWriter, r *http.Request) {
if repo := context.Get(r, "repository"); repo != nil { if repo := context.Get(r, "repository"); repo != nil {
@ -152,19 +163,13 @@ func RemoveRepository(w http.ResponseWriter, r *http.Request) {
var err error var err error
softDeletion := r.URL.Query().Get("setRemoved") == "1" err = helpers.Store(r).DeleteRepository(repository.ProjectID, repository.ID)
if err == db.ErrInvalidOperation {
if softDeletion { helpers.WriteJSON(w, http.StatusBadRequest, map[string]interface{}{
err = helpers.Store(r).DeleteRepositorySoft(repository.ProjectID, repository.ID) "error": "Repository is in use by one or more templates",
} else { "inUse": true,
err = helpers.Store(r).DeleteRepository(repository.ProjectID, repository.ID) })
if err == db.ErrInvalidOperation { return
helpers.WriteJSON(w, http.StatusBadRequest, map[string]interface{}{
"error": "Repository is in use by one or more templates",
"inUse": true,
})
return
}
} }
if err != nil { if err != nil {

View File

@ -161,6 +161,7 @@ func Route() *mux.Router {
projectKeyManagement.Use(projects.KeyMiddleware) projectKeyManagement.Use(projects.KeyMiddleware)
projectKeyManagement.HandleFunc("/{key_id}", projects.GetKeys).Methods("GET", "HEAD") 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.UpdateKey).Methods("PUT")
projectKeyManagement.HandleFunc("/{key_id}", projects.RemoveKey).Methods("DELETE") projectKeyManagement.HandleFunc("/{key_id}", projects.RemoveKey).Methods("DELETE")
@ -168,6 +169,7 @@ func Route() *mux.Router {
projectRepoManagement.Use(projects.RepositoryMiddleware) projectRepoManagement.Use(projects.RepositoryMiddleware)
projectRepoManagement.HandleFunc("/{repository_id}", projects.GetRepositories).Methods("GET", "HEAD") 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.UpdateRepository).Methods("PUT")
projectRepoManagement.HandleFunc("/{repository_id}", projects.RemoveRepository).Methods("DELETE") projectRepoManagement.HandleFunc("/{repository_id}", projects.RemoveRepository).Methods("DELETE")
@ -175,6 +177,7 @@ func Route() *mux.Router {
projectInventoryManagement.Use(projects.InventoryMiddleware) projectInventoryManagement.Use(projects.InventoryMiddleware)
projectInventoryManagement.HandleFunc("/{inventory_id}", projects.GetInventory).Methods("GET", "HEAD") 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.UpdateInventory).Methods("PUT")
projectInventoryManagement.HandleFunc("/{inventory_id}", projects.RemoveInventory).Methods("DELETE") projectInventoryManagement.HandleFunc("/{inventory_id}", projects.RemoveInventory).Methods("DELETE")
@ -182,6 +185,7 @@ func Route() *mux.Router {
projectEnvManagement.Use(projects.EnvironmentMiddleware) projectEnvManagement.Use(projects.EnvironmentMiddleware)
projectEnvManagement.HandleFunc("/{environment_id}", projects.GetEnvironment).Methods("GET", "HEAD") 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.UpdateEnvironment).Methods("PUT")
projectEnvManagement.HandleFunc("/{environment_id}", projects.RemoveEnvironment).Methods("DELETE") 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. // You should use methods SerializeSecret to fill this field.
Secret *string `db:"secret" json:"-"` Secret *string `db:"secret" json:"-"`
Removed bool `db:"removed" json:"removed"`
LoginPassword LoginPassword `db:"-" json:"login_password"` LoginPassword LoginPassword `db:"-" json:"login_password"`
SshKey SshKey `db:"-" json:"ssh"` SshKey SshKey `db:"-" json:"ssh"`
PAT string `db:"-" json:"pat"` PAT string `db:"-" json:"pat"`

View File

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

View File

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

View File

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

View File

@ -5,6 +5,7 @@ import (
"errors" "errors"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"reflect" "reflect"
"strings"
"time" "time"
) )
@ -39,18 +40,38 @@ type RetrieveQueryParams struct {
SortInverted bool 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 // It mainly used for NoSQL implementations (currently BoltDB) to preserve same
// data structure of different implementations and easy change it if required. // data structure of different implementations and easy change it if required.
type ObjectProperties struct { type ObjectProps struct {
TableName string TableName string
IsGlobal bool // doesn't belong to other table, for example to project or user. Type reflect.Type // to which type the table bust be mapped.
ForeignColumnSuffix string IsGlobal bool // doesn't belong to other table, for example to project or user.
PrimaryColumnName string ReferringColumnSuffix string
SortableColumns []string PrimaryColumnName string
DefaultSortingColumn string SortableColumns []string
SortInverted bool // sort from high to low object ID by default. It is useful for some NoSQL implementations. DefaultSortingColumn string
Type reflect.Type // to which type the table bust be mapped. 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") var ErrNotFound = errors.New("no rows in result set")
@ -76,38 +97,38 @@ type Store interface {
IsMigrationApplied(version Migration) (bool, error) IsMigrationApplied(version Migration) (bool, error)
// ApplyMigration runs executes a database migration // ApplyMigration runs executes a database migration
ApplyMigration(version Migration) error 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 // if a rollback exists
TryRollbackMigration(version Migration) TryRollbackMigration(version Migration)
GetEnvironment(projectID int, environmentID int) (Environment, error) GetEnvironment(projectID int, environmentID int) (Environment, error)
GetEnvironmentRefs(projectID int, environmentID int) (ObjectReferrers, error)
GetEnvironments(projectID int, params RetrieveQueryParams) ([]Environment, error) GetEnvironments(projectID int, params RetrieveQueryParams) ([]Environment, error)
UpdateEnvironment(env Environment) error UpdateEnvironment(env Environment) error
CreateEnvironment(env Environment) (Environment, error) CreateEnvironment(env Environment) (Environment, error)
DeleteEnvironment(projectID int, templateID int) error DeleteEnvironment(projectID int, templateID int) error
DeleteEnvironmentSoft(projectID int, templateID int) error
GetInventory(projectID int, inventoryID int) (Inventory, error) GetInventory(projectID int, inventoryID int) (Inventory, error)
GetInventoryRefs(projectID int, inventoryID int) (ObjectReferrers, error)
GetInventories(projectID int, params RetrieveQueryParams) ([]Inventory, error) GetInventories(projectID int, params RetrieveQueryParams) ([]Inventory, error)
UpdateInventory(inventory Inventory) error UpdateInventory(inventory Inventory) error
CreateInventory(inventory Inventory) (Inventory, error) CreateInventory(inventory Inventory) (Inventory, error)
DeleteInventory(projectID int, inventoryID int) error DeleteInventory(projectID int, inventoryID int) error
DeleteInventorySoft(projectID int, inventoryID int) error
GetRepository(projectID int, repositoryID int) (Repository, error) GetRepository(projectID int, repositoryID int) (Repository, error)
GetRepositoryRefs(projectID int, repositoryID int) (ObjectReferrers, error)
GetRepositories(projectID int, params RetrieveQueryParams) ([]Repository, error) GetRepositories(projectID int, params RetrieveQueryParams) ([]Repository, error)
UpdateRepository(repository Repository) error UpdateRepository(repository Repository) error
CreateRepository(repository Repository) (Repository, error) CreateRepository(repository Repository) (Repository, error)
DeleteRepository(projectID int, repositoryID int) error DeleteRepository(projectID int, repositoryID int) error
DeleteRepositorySoft(projectID int, repositoryID int) error
GetAccessKey(projectID int, accessKeyID int) (AccessKey, error) GetAccessKey(projectID int, accessKeyID int) (AccessKey, error)
GetAccessKeyRefs(projectID int, accessKeyID int) (ObjectReferrers, error)
GetAccessKeys(projectID int, params RetrieveQueryParams) ([]AccessKey, error) GetAccessKeys(projectID int, params RetrieveQueryParams) ([]AccessKey, error)
UpdateAccessKey(accessKey AccessKey) error UpdateAccessKey(accessKey AccessKey) error
CreateAccessKey(accessKey AccessKey) (AccessKey, error) CreateAccessKey(accessKey AccessKey) (AccessKey, error)
DeleteAccessKey(projectID int, accessKeyID int) error DeleteAccessKey(projectID int, accessKeyID int) error
DeleteAccessKeySoft(projectID int, accessKeyID int) error
GetUsers(params RetrieveQueryParams) ([]User, error) GetUsers(params RetrieveQueryParams) ([]User, error)
CreateUserWithoutPassword(user User) (User, error) CreateUserWithoutPassword(user User) (User, error)
@ -179,104 +200,128 @@ type Store interface {
SetViewPositions(projectID int, viewPositions map[int]int) error SetViewPositions(projectID int, viewPositions map[int]int) error
} }
var AccessKeyProps = ObjectProperties{ var AccessKeyProps = ObjectProps{
TableName: "access_key", TableName: "access_key",
SortableColumns: []string{"name", "type"}, Type: reflect.TypeOf(AccessKey{}),
ForeignColumnSuffix: "key_id", PrimaryColumnName: "id",
PrimaryColumnName: "id", ReferringColumnSuffix: "key_id",
Type: reflect.TypeOf(AccessKey{}), SortableColumns: []string{"name", "type"},
DefaultSortingColumn: "name", DefaultSortingColumn: "name",
} }
var EnvironmentProps = ObjectProperties{ var EnvironmentProps = ObjectProps{
TableName: "project__environment", TableName: "project__environment",
SortableColumns: []string{"name"}, Type: reflect.TypeOf(Environment{}),
ForeignColumnSuffix: "environment_id", PrimaryColumnName: "id",
PrimaryColumnName: "id", ReferringColumnSuffix: "environment_id",
Type: reflect.TypeOf(Environment{}), SortableColumns: []string{"name"},
DefaultSortingColumn: "name", DefaultSortingColumn: "name",
} }
var InventoryProps = ObjectProperties{ var InventoryProps = ObjectProps{
TableName: "project__inventory", TableName: "project__inventory",
SortableColumns: []string{"name"}, Type: reflect.TypeOf(Inventory{}),
ForeignColumnSuffix: "inventory_id", PrimaryColumnName: "id",
PrimaryColumnName: "id", ReferringColumnSuffix: "inventory_id",
Type: reflect.TypeOf(Inventory{}), SortableColumns: []string{"name"},
DefaultSortingColumn: "name", DefaultSortingColumn: "name",
} }
var RepositoryProps = ObjectProperties{ var RepositoryProps = ObjectProps{
TableName: "project__repository", TableName: "project__repository",
ForeignColumnSuffix: "repository_id", Type: reflect.TypeOf(Repository{}),
PrimaryColumnName: "id", PrimaryColumnName: "id",
Type: reflect.TypeOf(Repository{}), ReferringColumnSuffix: "repository_id",
DefaultSortingColumn: "name", DefaultSortingColumn: "name",
} }
var TemplateProps = ObjectProperties{ var TemplateProps = ObjectProps{
TableName: "project__template", TableName: "project__template",
SortableColumns: []string{"name"},
PrimaryColumnName: "id",
Type: reflect.TypeOf(Template{}), 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", PrimaryColumnName: "id",
Type: reflect.TypeOf(Project{}), SortableColumns: []string{"name"},
DefaultSortingColumn: "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", TableName: "user",
IsGlobal: true,
PrimaryColumnName: "id",
Type: reflect.TypeOf(User{}), Type: reflect.TypeOf(User{}),
}
var SessionProps = ObjectProperties{
TableName: "session",
PrimaryColumnName: "id", 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, 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", TableName: "task__output",
Type: reflect.TypeOf(TaskOutput{}), Type: reflect.TypeOf(TaskOutput{}),
} }
var ViewProps = ObjectProperties{ var ViewProps = ObjectProps{
TableName: "project__view", TableName: "project__view",
PrimaryColumnName: "id",
Type: reflect.TypeOf(View{}), Type: reflect.TypeOf(View{}),
PrimaryColumnName: "id",
DefaultSortingColumn: "position", 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"` RepositoryID int `db:"repository_id" json:"repository_id"`
EnvironmentID *int `db:"environment_id" json:"environment_id"` EnvironmentID *int `db:"environment_id" json:"environment_id"`
// Alias as described in https://github.com/ansible-semaphore/semaphore/issues/188 // Name as described in https://github.com/ansible-semaphore/semaphore/issues/188
Alias string `db:"alias" json:"alias"` Name string `db:"name" json:"name"`
// playbook name in the form of "some_play.yml" // playbook name in the form of "some_play.yml"
Playbook string `db:"playbook" json:"playbook"` Playbook string `db:"playbook" json:"playbook"`
// to fit into []string // to fit into []string
@ -74,8 +74,8 @@ type Template struct {
} }
func (tpl *Template) Validate() error { func (tpl *Template) Validate() error {
if tpl.Alias == "" { if tpl.Name == "" {
return &ValidationError{"template alias can not be empty"} return &ValidationError{"template name can not be empty"}
} }
if tpl.Playbook == "" { if tpl.Playbook == "" {

View File

@ -52,7 +52,7 @@ func (d strObjectID) ToBytes() []byte {
return []byte(d) return []byte(d)
} }
func makeBucketId(props db.ObjectProperties, ids ...int) []byte { func makeBucketId(props db.ObjectProps, ids ...int) []byte {
n := len(ids) n := len(ids)
id := props.TableName id := props.TableName
@ -107,7 +107,7 @@ func (d *BoltDb) IsInitialized() (initialized bool, err error) {
return 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 { err = d.db.View(func(tx *bbolt.Tx) error {
b := tx.Bucket(makeBucketId(props, bucketID)) b := tx.Bucket(makeBucketId(props, bucketID))
if b == nil { if b == nil {
@ -259,7 +259,7 @@ func marshalObject(obj interface{}) ([]byte, error) {
return json.Marshal(copyObject(obj, newType)) 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() objectsValue := reflect.ValueOf(objects).Elem()
objType := objectsValue.Type().Elem() objType := objectsValue.Type().Elem()
@ -317,7 +317,7 @@ func unmarshalObjects(rawData enumerable, props db.ObjectProperties, params db.R
return 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 { return d.db.View(func(tx *bbolt.Tx) error {
b := tx.Bucket(makeBucketId(props, bucketID)) b := tx.Bucket(makeBucketId(props, bucketID))
var c enumerable 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 { func (d *BoltDb) deleteObject(bucketID int, props db.ObjectProps, objectID objectID, tx *bbolt.Tx) error {
if props.ForeignColumnSuffix == "" { for _, u := range []db.ObjectProps{db.TemplateProps, db.EnvironmentProps, db.InventoryProps, db.RepositoryProps} {
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} {
inUse, err := d.isObjectInUse(bucketID, props, objectID, u) inUse, err := d.isObjectInUse(bucketID, props, objectID, u)
if err != nil { if err != nil {
return err return err
@ -422,55 +356,8 @@ func (d *BoltDb) deleteObject(bucketID int, props db.ObjectProperties, objectID
return d.db.Update(fn) 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. // 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 { return d.db.Update(func(tx *bbolt.Tx) error {
b := tx.Bucket(makeBucketId(props, bucketID)) b := tx.Bucket(makeBucketId(props, bucketID))
if b == nil { 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 { err := d.db.Update(func(tx *bbolt.Tx) error {
b, err := tx.CreateBucketIfNotExists(makeBucketId(props, bucketID)) 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 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 { func CreateTestStore() BoltDb {
r := rand.New(rand.NewSource(time.Now().UTC().UnixNano())) r := rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
fn := "/tmp/test_semaphore_db_" + strconv.Itoa(r.Int()) fn := "/tmp/test_semaphore_db_" + strconv.Itoa(r.Int())

View File

@ -17,48 +17,48 @@ type test1 struct {
Removed bool `db:"removed"` Removed bool `db:"removed"`
} }
var test1props = db.ObjectProperties{ //var test1props = db.ObjectProps{
IsGlobal: true, // IsGlobal: true,
TableName: "test1", // TableName: "test1",
PrimaryColumnName: "ID", // PrimaryColumnName: "ID",
} //}
func TestDeleteObjectSoft(t *testing.T) { //func TestDeleteObjectSoft(t *testing.T) {
store := CreateTestStore() // store := CreateTestStore()
//
obj := test1{ // obj := test1{
FirstName: "Denis", // FirstName: "Denis",
LastName: "Gukov", // LastName: "Gukov",
} // }
newObj, err := store.createObject(0, test1props, obj) // newObj, err := store.createObject(0, test1props, obj)
//
if err != nil { // if err != nil {
t.Fatal(err.Error()) // t.Fatal(err.Error())
} // }
//
objID := intObjectID(newObj.(test1).ID) // objID := intObjectID(newObj.(test1).ID)
//
err = store.deleteObjectSoft(0, test1props, objID) // err = store.deleteObjectSoft(0, test1props, objID)
//
if err != nil { // if err != nil {
t.Fatal(err.Error()) // t.Fatal(err.Error())
} // }
//
var found test1 // var found test1
err = store.getObject(0, test1props, objID, &found) // err = store.getObject(0, test1props, objID, &found)
//
if err != nil { // if err != nil {
t.Fatal(err.Error()) // t.Fatal(err.Error())
} // }
//
if found.ID != int(objID) || // if found.ID != int(objID) ||
found.Removed != true || // found.Removed != true ||
found.Password != obj.Password || // found.Password != obj.Password ||
found.LastName != obj.LastName { // found.LastName != obj.LastName {
//
t.Fatal() // t.Fatal()
} // }
} //}
func TestMarshalObject_UserWithPwd(t *testing.T) { func TestMarshalObject_UserWithPwd(t *testing.T) {
user := db.UserWithPwd{ user := db.UserWithPwd{
@ -200,7 +200,7 @@ func TestIsObjectInUse(t *testing.T) {
} }
_, err = store.CreateTemplate(db.Template{ _, err = store.CreateTemplate(db.Template{
Alias: "Test", Name: "Test",
Playbook: "test.yml", Playbook: "test.yml",
ProjectID: proj.ID, ProjectID: proj.ID,
InventoryID: 10, InventoryID: 10,
@ -236,7 +236,7 @@ func TestIsObjectInUse_Environment(t *testing.T) {
envID := 10 envID := 10
_, err = store.CreateTemplate(db.Template{ _, err = store.CreateTemplate(db.Template{
Alias: "Test", Name: "Test",
Playbook: "test.yml", Playbook: "test.yml",
ProjectID: proj.ID, ProjectID: proj.ID,
EnvironmentID: &envID, EnvironmentID: &envID,
@ -270,7 +270,7 @@ func TestIsObjectInUse_EnvironmentNil(t *testing.T) {
} }
_, err = store.CreateTemplate(db.Template{ _, err = store.CreateTemplate(db.Template{
Alias: "Test", Name: "Test",
Playbook: "test.yml", Playbook: "test.yml",
ProjectID: proj.ID, ProjectID: proj.ID,
EnvironmentID: nil, EnvironmentID: nil,
@ -351,3 +351,37 @@ func TestBoltDb_CreateAPIToken(t *testing.T) {
t.Fatal() 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{ build, err := store.CreateTemplate(db.Template{
ProjectID: 0, ProjectID: 0,
Type: db.TemplateBuild, Type: db.TemplateBuild,
Alias: "Build", Name: "Build",
Playbook: "build.yml", Playbook: "build.yml",
}) })
if err != nil { if err != nil {
@ -24,7 +24,7 @@ func TestTask_GetVersion(t *testing.T) {
ProjectID: 0, ProjectID: 0,
Type: db.TemplateDeploy, Type: db.TemplateDeploy,
BuildTemplateID: &build.ID, BuildTemplateID: &build.ID,
Alias: "Deploy", Name: "Deploy",
Playbook: "deploy.yml", Playbook: "deploy.yml",
}) })
if err != nil { if err != nil {
@ -35,7 +35,7 @@ func TestTask_GetVersion(t *testing.T) {
ProjectID: 0, ProjectID: 0,
Type: db.TemplateDeploy, Type: db.TemplateDeploy,
BuildTemplateID: &deploy.ID, BuildTemplateID: &deploy.ID,
Alias: "Deploy2", Name: "Deploy2",
Playbook: "deploy2.yml", Playbook: "deploy2.yml",
}) })
if err != nil { if err != nil {

View File

@ -13,6 +13,10 @@ func (d *BoltDb) GetAccessKey(projectID int, accessKeyID int) (key db.AccessKey,
return 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) { func (d *BoltDb) GetAccessKeys(projectID int, params db.RetrieveQueryParams) ([]db.AccessKey, error) {
var keys []db.AccessKey var keys []db.AccessKey
err := d.getObjects(projectID, db.AccessKeyProps, params, nil, &keys) 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 { func (d *BoltDb) DeleteAccessKey(projectID int, accessKeyID int) error {
return d.deleteObject(projectID, db.AccessKeyProps, intObjectID(accessKeyID), nil) 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 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) { func (d *BoltDb) GetEnvironments(projectID int, params db.RetrieveQueryParams) (environment []db.Environment, err error) {
err = d.getObjects(projectID, db.EnvironmentProps, params, nil, &environment) err = d.getObjects(projectID, db.EnvironmentProps, params, nil, &environment)
return return
@ -36,7 +40,3 @@ func (d *BoltDb) CreateEnvironment(env db.Environment) (db.Environment, error) {
func (d *BoltDb) DeleteEnvironment(projectID int, environmentID int) error { func (d *BoltDb) DeleteEnvironment(projectID int, environmentID int) error {
return d.deleteObject(projectID, db.EnvironmentProps, intObjectID(environmentID), nil) 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 return
} }
func (d *BoltDb) DeleteInventory(projectID int, inventoryID int) error { func (d *BoltDb) GetInventoryRefs(projectID int, inventoryID int) (db.ObjectReferrers, error) {
return d.deleteObject(projectID, db.InventoryProps, intObjectID(inventoryID), nil) return d.getObjectRefs(projectID, db.InventoryProps, inventoryID)
} }
func (d *BoltDb) DeleteInventorySoft(projectID int, inventoryID int) error { func (d *BoltDb) DeleteInventory(projectID int, inventoryID int) error {
return d.deleteObjectSoft(projectID, db.InventoryProps, intObjectID(inventoryID)) return d.deleteObject(projectID, db.InventoryProps, intObjectID(inventoryID), nil)
} }
func (d *BoltDb) UpdateInventory(inventory db.Inventory) error { 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 return false, err
} }
func (d *BoltDb) ApplyMigration(migration db.Migration) (err error) { func (d *BoltDb) ApplyMigration(m db.Migration) (err error) {
switch migration.Version { switch m.Version {
case "2.8.26": 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 { if err != nil {
@ -50,18 +50,66 @@ func (d *BoltDb) ApplyMigration(migration db.Migration) (err error) {
return err return err
} }
j, err := json.Marshal(migration) j, err := json.Marshal(m)
if err != nil { if err != nil {
return err return err
} }
return b.Put([]byte(migration.Version), j) return b.Put([]byte(m.Version), j)
}) })
} }
func (d *BoltDb) TryRollbackMigration(migration db.Migration) { func (d *BoltDb) TryRollbackMigration(m db.Migration) {
switch migration.Version { switch m.Version {
case "2.8.26": 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) t.Fatal(err)
} }
err = Migration_2_8_28{DB: store.db}.Apply() err = migration_2_8_28{migration{store.db}}.Apply()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -77,7 +77,7 @@ func TestMigration_2_8_28_Apply2(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
err = Migration_2_8_28{DB: store.db}.Apply() err = migration_2_8_28{migration{store.db}}.Apply()
if err != nil { if err != nil {
t.Fatal(err) 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 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) { func (d *BoltDb) GetRepositories(projectID int, params db.RetrieveQueryParams) (repositories []db.Repository, err error) {
err = d.getObjects(projectID, db.RepositoryProps, params, nil, &repositories) err = d.getObjects(projectID, db.RepositoryProps, params, nil, &repositories)
return return
@ -38,7 +42,3 @@ func (d *BoltDb) CreateRepository(repository db.Repository) (db.Repository, erro
func (d *BoltDb) DeleteRepository(projectID int, repositoryId int) error { func (d *BoltDb) DeleteRepository(projectID int, repositoryId int) error {
return d.deleteObject(projectID, db.RepositoryProps, intObjectID(repositoryId), nil) 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"` UserID int `db:"user_id" json:"user_id"`
} }
var globalTokenObject = db.ObjectProperties{ var globalTokenObject = db.ObjectProps{
TableName: "token", TableName: "token",
PrimaryColumnName: "id", PrimaryColumnName: "id",
Type: reflect.TypeOf(globalToken{}), 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] = db.TaskWithTpl{Task: task}
tasksWithTpl[i].TemplatePlaybook = tpl.Playbook tasksWithTpl[i].TemplatePlaybook = tpl.Playbook
tasksWithTpl[i].TemplateAlias = tpl.Alias tasksWithTpl[i].TemplateAlias = tpl.Name
tasksWithTpl[i].TemplateType = tpl.Type tasksWithTpl[i].TemplateType = tpl.Type
if task.UserID != nil { if task.UserID != nil {
usr, ok := users[*task.UserID] usr, ok := users[*task.UserID]

View File

@ -11,6 +11,7 @@ import (
"github.com/gobuffalo/packr" "github.com/gobuffalo/packr"
_ "github.com/lib/pq" _ "github.com/lib/pq"
"github.com/masterminds/squirrel" "github.com/masterminds/squirrel"
"reflect"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
@ -182,7 +183,7 @@ func createDb() error {
return nil 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("*"). q := squirrel.Select("*").
From(props.TableName). From(props.TableName).
Where("id=?", objectID) Where("id=?", objectID)
@ -208,7 +209,7 @@ func (d *SqlDb) getObject(projectID int, props db.ObjectProperties, objectID int
return 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("*"). q := squirrel.Select("*").
From(props.TableName+" pe"). From(props.TableName+" pe").
Where("pe.project_id=?", projectID) Where("pe.project_id=?", projectID)
@ -238,7 +239,7 @@ func (d *SqlDb) getObjects(projectID int, props db.ObjectProperties, params db.R
return 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( return validateMutationResult(
d.exec( d.exec(
"delete from "+props.TableName+" where project_id=? and id=?", "delete from "+props.TableName+" where project_id=? and id=?",
@ -246,14 +247,6 @@ func (d *SqlDb) deleteObject(projectID int, props db.ObjectProperties, objectID
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 { func (d *SqlDb) Close() error {
return d.sql.Db.Close() return d.sql.Db.Close()
} }
@ -338,6 +331,118 @@ func getSqlForTable(tableName string, p db.RetrieveQueryParams) (string, []inter
return q.ToSql() 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 { func (d *SqlDb) Sql() *gorp.DbMap {
return d.sql return d.sql
} }

View File

@ -15,6 +15,10 @@ func (d *SqlDb) GetAccessKey(projectID int, accessKeyID int) (key db.AccessKey,
return 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) { func (d *SqlDb) GetAccessKeys(projectID int, params db.RetrieveQueryParams) ([]db.AccessKey, error) {
var keys []db.AccessKey var keys []db.AccessKey
err := d.getObjects(projectID, db.AccessKeyProps, params, &keys) 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 { func (d *SqlDb) DeleteAccessKey(projectID int, accessKeyID int) error {
return d.deleteObject(projectID, db.AccessKeyProps, accessKeyID) 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 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) { func (d *SqlDb) GetEnvironment(projectID int, environmentID int) (db.Environment, error) {
var environment db.Environment var environment db.Environment
@ -8,6 +10,10 @@ func (d *SqlDb) GetEnvironment(projectID int, environmentID int) (db.Environment
return environment, err 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) { func (d *SqlDb) GetEnvironments(projectID int, params db.RetrieveQueryParams) ([]db.Environment, error) {
var environment []db.Environment var environment []db.Environment
err := d.getObjects(projectID, db.EnvironmentProps, params, &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 { func (d *SqlDb) DeleteEnvironment(projectID int, environmentID int) error {
return d.deleteObject(projectID, db.EnvironmentProps, environmentID) 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 return inventories, err
} }
func (d *SqlDb) DeleteInventory(projectID int, inventoryID int) error { func (d *SqlDb) GetInventoryRefs(projectID int, inventoryID int) (db.ObjectReferrers, error) {
return d.deleteObject(projectID, db.InventoryProps, inventoryID) return d.getObjectRefs(projectID, db.InventoryProps, inventoryID)
} }
func (d *SqlDb) DeleteInventorySoft(projectID int, inventoryID int) error { func (d *SqlDb) DeleteInventory(projectID int, inventoryID int) error {
return d.deleteObjectSoft(projectID, db.InventoryProps, inventoryID) return d.deleteObject(projectID, db.InventoryProps, inventoryID)
} }
func (d *SqlDb) UpdateInventory(inventory db.Inventory) error { 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 newInventory.ID = insertID
return return
} }

View File

@ -18,6 +18,7 @@ var (
longtextRE = regexp.MustCompile(`(?i)\blongtext\b`) longtextRE = regexp.MustCompile(`(?i)\blongtext\b`)
ifExistsRE = regexp.MustCompile(`(?i)\bif exists\b`) ifExistsRE = regexp.MustCompile(`(?i)\bif exists\b`)
dropForeignKey = regexp.MustCompile(`(?i)\bdrop foreign key\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 // getVersionPath is the humanoid version with the file format appended
@ -38,6 +39,9 @@ func getVersionSQL(path string) (queries []string) {
panic(err) panic(err)
} }
queries = strings.Split(strings.ReplaceAll(sql, ";\r\n", ";\n"), ";\n") queries = strings.Split(strings.ReplaceAll(sql, ";\r\n", ";\n"), ";\n")
for i := range queries {
queries[i] = strings.Trim(queries[i], "\n\t ")
}
return return
} }
@ -49,12 +53,41 @@ func (d *SqlDb) prepareMigration(query string) string {
query = autoIncrementRE.ReplaceAllString(query, "auto_increment") query = autoIncrementRE.ReplaceAllString(query, "auto_increment")
query = ifExistsRE.ReplaceAllString(query, "") query = ifExistsRE.ReplaceAllString(query, "")
case gorp.PostgresDialect: case gorp.PostgresDialect:
query = serialRE.ReplaceAllString(query, "serial primary key") m := changeRE.FindStringSubmatch(query)
query = identifierQuoteRE.ReplaceAllString(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 = dateTimeTypeRE.ReplaceAllString(query, "timestamp")
query = tinyintRE.ReplaceAllString(query, "smallint") query = tinyintRE.ReplaceAllString(query, "smallint")
query = longtextRE.ReplaceAllString(query, "text") query = longtextRE.ReplaceAllString(query, "text")
query = serialRE.ReplaceAllString(query, "serial primary key")
query = dropForeignKey.ReplaceAllString(query, "drop constraint") query = dropForeignKey.ReplaceAllString(query, "drop constraint")
query = identifierQuoteRE.ReplaceAllString(query, "\"")
} }
return query return query
} }
@ -130,7 +163,7 @@ func (d *SqlDb) ApplyMigration(migration db.Migration) error {
switch migration.Version { switch migration.Version {
case "2.8.26": 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 { if err != nil {

View File

@ -5,12 +5,12 @@ import (
"strings" "strings"
) )
type Migration_2_8_26 struct { type migration_2_8_26 struct {
DB *SqlDb db *SqlDb
} }
func (m Migration_2_8_26) Apply(tx *gorp.Transaction) error { 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")) rows, err := tx.Query(m.db.PrepareQuery("SELECT id, git_url FROM project__repository"))
if err != nil { if err != nil {
return err return err
} }
@ -39,7 +39,7 @@ func (m Migration_2_8_26) Apply(tx *gorp.Transaction) error {
if len(parts) > 1 { if len(parts) > 1 {
url, branch = parts[0], 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 = ? " + "SET git_url = ?, git_branch = ? " +
"WHERE id = ?") "WHERE id = ?")
_, err = tx.Exec(q, url, branch, 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`; 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 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) { func (d *SqlDb) GetRepositories(projectID int, params db.RetrieveQueryParams) (repositories []db.Repository, err error) {
q := squirrel.Select("*"). q := squirrel.Select("*").
From("project__repository pr") 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 { func (d *SqlDb) DeleteRepository(projectID int, repositoryId int) error {
return d.deleteObject(projectID, db.RepositoryProps, repositoryId) 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 := "task.*"
fields += ", tpl.playbook as tpl_playbook" + fields += ", tpl.playbook as tpl_playbook" +
", `user`.name as user_name" + ", `user`.name as user_name" +
", tpl.alias as tpl_alias" + ", tpl.name as tpl_alias" +
", tpl.type as tpl_type" ", tpl.type as tpl_type"
q := squirrel.Select(fields). 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( insertID, err := d.insert(
"id", "id",
"insert into project__template (project_id, inventory_id, repository_id, environment_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)"+ "build_template_id, view_id, autorun, survey_vars)"+
"values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", "values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
template.ProjectID, template.ProjectID,
template.InventoryID, template.InventoryID,
template.RepositoryID, template.RepositoryID,
template.EnvironmentID, template.EnvironmentID,
template.Alias, template.Name,
template.Playbook, template.Playbook,
template.Arguments, template.Arguments,
template.AllowOverrideArgsInTask, template.AllowOverrideArgsInTask,
@ -63,7 +63,7 @@ func (d *SqlDb) UpdateTemplate(template db.Template) error {
"inventory_id=?, "+ "inventory_id=?, "+
"repository_id=?, "+ "repository_id=?, "+
"environment_id=?, "+ "environment_id=?, "+
"alias=?, "+ "name=?, "+
"playbook=?, "+ "playbook=?, "+
"arguments=?, "+ "arguments=?, "+
"allow_override_args_in_task=?, "+ "allow_override_args_in_task=?, "+
@ -79,7 +79,7 @@ func (d *SqlDb) UpdateTemplate(template db.Template) error {
template.InventoryID, template.InventoryID,
template.RepositoryID, template.RepositoryID,
template.EnvironmentID, template.EnvironmentID,
template.Alias, template.Name,
template.Playbook, template.Playbook,
template.Arguments, template.Arguments,
template.AllowOverrideArgsInTask, template.AllowOverrideArgsInTask,
@ -103,7 +103,7 @@ func (d *SqlDb) GetTemplates(projectID int, filter db.TemplateFilter, params db.
"pt.inventory_id", "pt.inventory_id",
"pt.repository_id", "pt.repository_id",
"pt.environment_id", "pt.environment_id",
"pt.alias", "pt.name",
"pt.playbook", "pt.playbook",
"pt.arguments", "pt.arguments",
"pt.allow_override_args_in_task", "pt.allow_override_args_in_task",
@ -129,7 +129,7 @@ func (d *SqlDb) GetTemplates(projectID int, filter db.TemplateFilter, params db.
} }
switch params.SortBy { switch params.SortBy {
case "alias", "playbook": case "name", "playbook":
q = q.Where("pt.project_id=?", projectID). q = q.Where("pt.project_id=?", projectID).
OrderBy("pt." + params.SortBy + " " + order) OrderBy("pt." + params.SortBy + " " + order)
case "inventory": case "inventory":
@ -146,7 +146,7 @@ func (d *SqlDb) GetTemplates(projectID int, filter db.TemplateFilter, params db.
OrderBy("pr.name " + order) OrderBy("pr.name " + order)
default: default:
q = q.Where("pt.project_id=?", projectID). q = q.Where("pt.project_id=?", projectID).
OrderBy("pt.alias " + order) OrderBy("pt.name " + order)
} }
query, args, err := q.ToSql() query, args, err := q.ToSql()

View File

@ -10,17 +10,17 @@ import (
"github.com/ansible-semaphore/semaphore/util" "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>` 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 // Alert represents an alert that will be templated and sent to the appropriate service
type Alert struct { type Alert struct {
TaskID string TaskID string
Alias string Name string
TaskURL string TaskURL string
ChatID string ChatID string
TaskResult string TaskResult string
@ -39,7 +39,7 @@ func (t *TaskRunner) sendMailAlert() {
var mailBuffer bytes.Buffer var mailBuffer bytes.Buffer
alert := Alert{ alert := Alert{
TaskID: strconv.Itoa(t.task.ID), TaskID: strconv.Itoa(t.task.ID),
Alias: t.template.Alias, Name: t.template.Name,
TaskURL: util.Config.WebHost + "/project/" + strconv.Itoa(t.template.ProjectID), TaskURL: util.Config.WebHost + "/project/" + strconv.Itoa(t.template.ProjectID),
} }
tpl := template.New("mail body template") tpl := template.New("mail body template")
@ -103,7 +103,7 @@ func (t *TaskRunner) sendTelegramAlert() {
alert := Alert{ alert := Alert{
TaskID: strconv.Itoa(t.task.ID), 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), TaskURL: util.Config.WebHost + "/project/" + strconv.Itoa(t.template.ProjectID) + "/templates/" + strconv.Itoa(t.template.ID) + "?t=" + strconv.Itoa(t.task.ID),
ChatID: chatID, ChatID: chatID,
TaskResult: strings.ToUpper(t.task.Status), TaskResult: strings.ToUpper(t.task.Status),

View File

@ -18,14 +18,25 @@ type logRecord struct {
} }
type TaskPool struct { type TaskPool struct {
queue []*TaskRunner // queue contains list of tasks in status TaskWaitingStatus.
register chan *TaskRunner queue []*TaskRunner
activeProj map[int]*TaskRunner
activeNodes map[string]*TaskRunner // register channel used to put tasks to queue.
running int 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 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 { type resourceLock struct {

View File

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

View File

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

View File

@ -51,7 +51,7 @@
class="breadcrumbs__item breadcrumbs__item--link" class="breadcrumbs__item breadcrumbs__item--link"
:to="`/project/${projectId}/templates/${template ? template.id : null}`" :to="`/project/${projectId}/templates/${template ? template.id : null}`"
@click="taskLogDialog = false" @click="taskLogDialog = false"
>{{ template ? template.alias : null }} >{{ template ? template.name : null }}
</router-link> </router-link>
<v-icon>mdi-chevron-right</v-icon> <v-icon>mdi-chevron-right</v-icon>
<span class="breadcrumbs__item">Task #{{ task ? task.id : null }}</span> <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" color="blue darken-1"
text text
@click="needSave = true" @click="needSave = true"
v-if="saveButtonText != null"
> >
{{ saveButtonText }} {{ saveButtonText }}
</v-btn> </v-btn>

View File

@ -2,12 +2,15 @@ import axios from 'axios';
import EventBus from '@/event-bus'; import EventBus from '@/event-bus';
import EditDialog from '@/components/EditDialog.vue'; import EditDialog from '@/components/EditDialog.vue';
import YesNoDialog from '@/components/YesNoDialog.vue'; import YesNoDialog from '@/components/YesNoDialog.vue';
import ObjectRefsView from '@/components/ObjectRefsView.vue';
import { getErrorMessage } from '@/lib/error'; import { getErrorMessage } from '@/lib/error';
export default { export default {
components: { components: {
YesNoDialog, YesNoDialog,
EditDialog, EditDialog,
ObjectRefsView,
}, },
props: { props: {
@ -19,9 +22,13 @@ export default {
return { return {
headers: this.getHeaders(), headers: this.getHeaders(),
items: null, items: null,
itemId: null, itemId: null,
editDialog: null, editDialog: null,
deleteItemDialog: null, deleteItemDialog: null,
itemRefs: null,
itemRefsDialog: null,
}; };
}, },
@ -32,7 +39,8 @@ export default {
methods: { methods: {
// eslint-disable-next-line no-empty-function // eslint-disable-next-line no-empty-function
async beforeLoadItems() { }, async beforeLoadItems() {
},
getSingleItemUrl() { getSingleItemUrl() {
throw new Error('Not implemented'); throw new Error('Not implemented');
@ -54,8 +62,23 @@ export default {
await this.loadItems(); await this.loadItems();
}, },
askDeleteItem(itemId) { async askDeleteItem(itemId) {
this.itemId = 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; 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={}> <template v-slot:title={}>
<v-icon class="mr-4">{{ TEMPLATE_TYPE_ICONS[template.type] }}</v-icon> <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> <v-icon>mdi-chevron-right</v-icon>
<span class="breadcrumbs__item">New Task</span> <span class="breadcrumbs__item">New Task</span>
</template> </template>

View File

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

View File

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

View File

@ -18,6 +18,20 @@
</template> </template>
</EditDialog> </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 <YesNoDialog
title="Delete repository" title="Delete repository"
text="Are you really want to delete this repository?" text="Are you really want to delete this repository?"

View File

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

View File

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