Merge pull request #2395 from semaphoreui/refactor_backups

refactor backups
This commit is contained in:
Denis Gukov 2024-10-07 17:08:30 +05:00 committed by GitHub
commit c7aa45516d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 719 additions and 306 deletions

View File

@ -136,7 +136,7 @@ definitions:
ProjectBackup:
type: object
example: {"meta":{"name":"homelab","alert":true,"alert_chat":"Test","max_parallel_tasks":0},"templates":[{"inventory":"Build","repository":"Demo","environment":"Empty","name":"Build","playbook":"build.yml","arguments":"[]","allow_override_args_in_task":false,"description":"Build Job","vault_key":null,"type":"build","start_version":"1.0.0","build_template":null,"view":"Build","autorun":false,"survey_vars":"null","suppress_success_alerts":false,"cron":"* * * * *"}],"repositories":[{"name":"Demo","git_url":"https://github.com/semaphoreui/demo-project.git","git_branch":"main","ssh_key":"None"}],"keys":[{"name":"None","type":"none"},{"name":"Vault Password","type":"login_password"}],"views":[{"name":"Build","position":0}],"inventories":[{"name":"Build","inventory":"","ssh_key":"None","become_key":"None","type":"static"},{"name":"Dev","inventory":"","ssh_key":"None","become_key":"None","type":"file"},{"name":"Prod","inventory":"","ssh_key":"None","become_key":"None","type":"file"}],"environments":[{"name":"Empty","password":null,"json":"{}","env":null}]}
example: {"meta":{"name":"homelab","alert":true,"alert_chat":"Test","max_parallel_tasks":0},"templates":[{"inventory":"Build","repository":"Demo","environment":"Empty","name":"Build","playbook":"build.yml","arguments":"[]","allow_override_args_in_task":false,"description":"Build Job","vault_key":null,"type":"build","start_version":"1.0.0","build_template":null,"view":"Build","autorun":false,"survey_vars":"null","suppress_success_alerts":false,"cron":"* * * * *"}],"repositories":[{"name":"Demo","git_url":"https://github.com/semaphoreui/demo-project.git","git_branch":"main","ssh_key":"None"}],"keys":[{"name":"None","type":"none"},{"name":"Vault Password","type":"login_password"}],"views":[{"title":"Build","position":0}],"inventories":[{"name":"Build","inventory":"","ssh_key":"None","become_key":"None","type":"static"},{"name":"Dev","inventory":"","ssh_key":"None","become_key":"None","type":"file"},{"name":"Prod","inventory":"","ssh_key":"None","become_key":"None","type":"file"}],"environments":[{"name":"Empty","password":null,"json":"{}","env":null}]}
properties:
meta:
type: object

View File

@ -87,7 +87,8 @@ func WriteJSON(w http.ResponseWriter, code int, out interface{}) {
w.WriteHeader(code)
if err := json.NewEncoder(w).Encode(out); err != nil {
panic(err)
log.Error(err)
debug.PrintStack()
}
}

View File

@ -1,7 +1,9 @@
package projects
import (
"io"
"net/http"
"strings"
"github.com/ansible-semaphore/semaphore/api/helpers"
"github.com/ansible-semaphore/semaphore/db"
@ -21,27 +23,47 @@ func GetBackup(w http.ResponseWriter, r *http.Request) {
helpers.WriteError(w, err)
return
}
helpers.WriteJSON(w, http.StatusOK, backup)
str, err := backup.Marshal()
if err != nil {
helpers.WriteError(w, err)
return
}
w.Header().Set("content-type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(str))
}
func Restore(w http.ResponseWriter, r *http.Request) {
user := context.Get(r, "user").(*db.User)
var backup projectService.BackupFormat
var p *db.Project
var err error
if !helpers.Bind(w, r, &backup) {
helpers.WriteJSON(w, http.StatusBadRequest, backup)
return
}
store := helpers.Store(r)
if err = backup.Verify(); err != nil {
buf := new(strings.Builder)
if _, err := io.Copy(buf, r.Body); err != nil {
log.Error(err)
helpers.WriteError(w, err)
return
}
if p, err = backup.Restore(*user, store); err != nil {
if err := backup.Unmarshal(buf.String()); err != nil {
log.Error(err)
helpers.WriteError(w, err)
return
}
store := helpers.Store(r)
if err := backup.Verify(); err != nil {
log.Error(err)
helpers.WriteError(w, err)
return
}
var p *db.Project
p, err := backup.Restore(*user, store)
if err != nil {
log.Error(err)
helpers.WriteError(w, err)
return

View File

@ -26,24 +26,29 @@ const (
// AccessKey represents a key used to access a machine with ansible from semaphore
type AccessKey struct {
ID int `db:"id" json:"id"`
ID int `db:"id" json:"id" backup:"-"`
Name string `db:"name" json:"name" binding:"required"`
// 'ssh/login_password/none'
Type AccessKeyType `db:"type" json:"type" binding:"required"`
ProjectID *int `db:"project_id" json:"project_id"`
ProjectID *int `db:"project_id" json:"project_id" backup:"-"`
// Secret used internally, do not assign this field.
// You should use methods SerializeSecret to fill this field.
Secret *string `db:"secret" json:"-"`
Secret *string `db:"secret" json:"-" backup:"-"`
String string `db:"-" json:"string"`
LoginPassword LoginPassword `db:"-" json:"login_password"`
SshKey SshKey `db:"-" json:"ssh"`
OverrideSecret bool `db:"-" json:"override_secret"`
EnvironmentID *int `db:"environment_id" json:"-"`
UserID *int `db:"user_id" json:"-"`
// EnvironmentID is an ID of environment which owns the access key.
EnvironmentID *int `db:"environment_id" json:"-" backup:"-"`
// UserID is an ID of user which owns the access key.
UserID *int `db:"user_id" json:"-" backup:"-"`
Empty bool `db:"-" json:"empty"`
}
type LoginPassword struct {

View File

@ -31,13 +31,15 @@ type EnvironmentSecret struct {
// Environment is used to pass additional arguments, in json form to ansible
type Environment struct {
ID int `db:"id" json:"id"`
Name string `db:"name" json:"name" binding:"required"`
ProjectID int `db:"project_id" json:"project_id"`
Password *string `db:"password" json:"password"`
JSON string `db:"json" json:"json" binding:"required"`
ENV *string `db:"env" json:"env" binding:"required"`
Secrets []EnvironmentSecret `db:"-" json:"secrets"`
ID int `db:"id" json:"id" backup:"-"`
Name string `db:"name" json:"name" binding:"required"`
ProjectID int `db:"project_id" json:"project_id" backup:"-"`
Password *string `db:"password" json:"password"`
JSON string `db:"json" json:"json" binding:"required"`
ENV *string `db:"env" json:"env" binding:"required"`
// Secrets is a field which used to update secrets associated with the environment.
Secrets []EnvironmentSecret `db:"-" json:"secrets" backup:"-"`
}
func (s *EnvironmentSecret) Validate() error {

View File

@ -14,17 +14,17 @@ const (
// Inventory is the model of an ansible inventory file
type Inventory struct {
ID int `db:"id" json:"id"`
ID int `db:"id" json:"id" backup:"-"`
Name string `db:"name" json:"name" binding:"required"`
ProjectID int `db:"project_id" json:"project_id"`
ProjectID int `db:"project_id" json:"project_id" backup:"-"`
Inventory string `db:"inventory" json:"inventory"`
// accesses hosts in inventory
SSHKeyID *int `db:"ssh_key_id" json:"ssh_key_id"`
SSHKey AccessKey `db:"-" json:"-"`
SSHKeyID *int `db:"ssh_key_id" json:"ssh_key_id" backup:"-"`
SSHKey AccessKey `db:"-" json:"-" backup:"-"`
BecomeKeyID *int `db:"become_key_id" json:"become_key_id"`
BecomeKey AccessKey `db:"-" json:"-"`
BecomeKeyID *int `db:"become_key_id" json:"become_key_id" backup:"-"`
BecomeKey AccessKey `db:"-" json:"-" backup:"-"`
// static/file
Type InventoryType `db:"type" json:"type"`
@ -33,12 +33,12 @@ type Inventory struct {
// It is not used now but can be used in feature for
// inventories which can not be used more than one template
// at once.
HolderID *int `db:"holder_id" json:"holder_id"`
HolderID *int `db:"holder_id" json:"holder_id" backup:"-"`
// RepositoryID is an ID of repo where inventory stored.
// If null than inventory will be got from template repository.
RepositoryID *int `db:"repository_id" json:"repository_id"`
Repository *Repository `db:"-" json:"-"`
RepositoryID *int `db:"repository_id" json:"repository_id" backup:"-"`
Repository *Repository `db:"-" json:"-" backup:"-"`
}
func (e Inventory) GetFilename() string {

View File

@ -6,9 +6,9 @@ import (
// Project is the top level structure in Semaphore
type Project struct {
ID int `db:"id" json:"id"`
ID int `db:"id" json:"id" backup:"-"`
Name string `db:"name" json:"name" binding:"required"`
Created time.Time `db:"created" json:"created"`
Created time.Time `db:"created" json:"created" backup:"-"`
Alert bool `db:"alert" json:"alert"`
AlertChat *string `db:"alert_chat" json:"alert_chat"`
MaxParallelTasks int `db:"max_parallel_tasks" json:"max_parallel_tasks"`

View File

@ -23,14 +23,14 @@ const (
// Repository is the model for code stored in a git repository
type Repository struct {
ID int `db:"id" json:"id"`
ID int `db:"id" json:"id" backup:"-"`
Name string `db:"name" json:"name" binding:"required"`
ProjectID int `db:"project_id" json:"project_id"`
ProjectID int `db:"project_id" json:"project_id" backup:"-"`
GitURL string `db:"git_url" json:"git_url" binding:"required"`
GitBranch string `db:"git_branch" json:"git_branch" binding:"required"`
SSHKeyID int `db:"ssh_key_id" json:"ssh_key_id" binding:"required"`
SSHKeyID int `db:"ssh_key_id" json:"ssh_key_id" binding:"required" backup:"-"`
SSHKey AccessKey `db:"-" json:"-"`
SSHKey AccessKey `db:"-" json:"-" backup:"-"`
}
func (r Repository) ClearCache() error {

View File

@ -1,15 +1,15 @@
package db
type Schedule struct {
ID int `db:"id" json:"id"`
ProjectID int `db:"project_id" json:"project_id"`
TemplateID int `db:"template_id" json:"template_id"`
ID int `db:"id" json:"id" backup:"-"`
ProjectID int `db:"project_id" json:"project_id" backup:"-"`
TemplateID int `db:"template_id" json:"template_id" backup:"-"`
CronFormat string `db:"cron_format" json:"cron_format"`
Name string `db:"name" json:"name"`
Active bool `db:"active" json:"active"`
LastCommitHash *string `db:"last_commit_hash" json:"-"`
RepositoryID *int `db:"repository_id" json:"repository_id"`
LastCommitHash *string `db:"last_commit_hash" json:"-" backup:"-"`
RepositoryID *int `db:"repository_id" json:"repository_id" backup:"-"`
}
type ScheduleWithTpl struct {

View File

@ -23,7 +23,9 @@ func GetParsedTime(t time.Time) time.Time {
}
func ObjectToJSON(obj interface{}) *string {
if obj == nil || (reflect.ValueOf(obj).Kind() == reflect.Ptr && reflect.ValueOf(obj).IsNil()) {
if obj == nil ||
(reflect.ValueOf(obj).Kind() == reflect.Ptr && reflect.ValueOf(obj).IsNil()) ||
(reflect.ValueOf(obj).Kind() == reflect.Slice && reflect.ValueOf(obj).IsZero()) {
return nil
}
bytes, err := json.Marshal(obj)

View File

@ -58,12 +58,12 @@ type TemplateFilter struct {
// Template is a user defined model that is used to run a task
type Template struct {
ID int `db:"id" json:"id"`
ID int `db:"id" json:"id" backup:"-"`
ProjectID int `db:"project_id" json:"project_id"`
InventoryID *int `db:"inventory_id" json:"inventory_id"`
RepositoryID int `db:"repository_id" json:"repository_id"`
EnvironmentID *int `db:"environment_id" json:"environment_id"`
ProjectID int `db:"project_id" json:"project_id" backup:"-"`
InventoryID *int `db:"inventory_id" json:"inventory_id" backup:"-"`
RepositoryID int `db:"repository_id" json:"repository_id" backup:"-"`
EnvironmentID *int `db:"environment_id" json:"environment_id" backup:"-"`
// Name as described in https://github.com/ansible-semaphore/semaphore/issues/188
Name string `db:"name" json:"name"`
@ -76,15 +76,15 @@ type Template struct {
Description *string `db:"description" json:"description"`
Vaults []TemplateVault `db:"-" json:"vaults"`
Vaults []TemplateVault `db:"-" json:"vaults" backup:"-"`
Type TemplateType `db:"type" json:"type"`
StartVersion *string `db:"start_version" json:"start_version"`
BuildTemplateID *int `db:"build_template_id" json:"build_template_id"`
BuildTemplateID *int `db:"build_template_id" json:"build_template_id" backup:"-"`
ViewID *int `db:"view_id" json:"view_id"`
ViewID *int `db:"view_id" json:"view_id" backup:"-"`
LastTask *TaskWithTpl `db:"-" json:"last_task"`
LastTask *TaskWithTpl `db:"-" json:"last_task" backup:"-"`
Autorun bool `db:"autorun" json:"autorun"`
@ -92,13 +92,13 @@ type Template struct {
// It is not used for store survey vars to database.
// Do not use it in your code. Use SurveyVars instead.
SurveyVarsJSON *string `db:"survey_vars" json:"-"`
SurveyVars []SurveyVar `db:"-" json:"survey_vars"`
SurveyVars []SurveyVar `db:"-" json:"survey_vars" backup:"-"`
SuppressSuccessAlerts bool `db:"suppress_success_alerts" json:"suppress_success_alerts"`
App TemplateApp `db:"app" json:"app"`
Tasks int `db:"tasks" json:"tasks"`
Tasks int `db:"tasks" json:"tasks" backup:"-"`
}
func (tpl *Template) Validate() error {

View File

@ -1,10 +1,10 @@
package db
type TemplateVault struct {
ID int `db:"id" json:"id"`
ProjectID int `db:"project_id" json:"project_id"`
TemplateID int `db:"template_id" json:"template_id"`
VaultKeyID int `db:"vault_key_id" json:"vault_key_id"`
ID int `db:"id" json:"id" backup:"-"`
ProjectID int `db:"project_id" json:"project_id" backup:"-"`
TemplateID int `db:"template_id" json:"template_id" backup:"-"`
VaultKeyID int `db:"vault_key_id" json:"vault_key_id" backup:"-"`
Name *string `db:"name" json:"name"`
Vault *AccessKey `db:"-" json:"-"`

View File

@ -1,8 +1,8 @@
package db
type View struct {
ID int `db:"id" json:"id"`
ProjectID int `db:"project_id" json:"project_id"`
ID int `db:"id" json:"id" backup:"-"`
ProjectID int `db:"project_id" json:"project_id" backup:"-"`
Title string `db:"title" json:"title"`
Position int `db:"position" json:"position"`
}
@ -12,4 +12,4 @@ func (view *View) Validate() error {
return &ValidationError{"title can not be empty"}
}
return nil
}
}

View File

@ -78,6 +78,10 @@ func (d *BoltDb) GetTemplates(projectID int, filter db.TemplateFilter, params db
err = d.apply(projectID, db.TaskProps, db.RetrieveQueryParams{}, func(i interface{}) error {
task := i.(db.Task)
if task.ProjectID != projectID {
return nil
}
tpl, ok := templatesMap[task.TemplateID]
if !ok {
return nil

View File

@ -3,7 +3,6 @@ package bolt
import (
"github.com/ansible-semaphore/semaphore/db"
"go.etcd.io/bbolt"
"slices"
)
func (d *BoltDb) GetTemplateVaults(projectID int, templateID int) (vaults []db.TemplateVault, err error) {
@ -13,8 +12,8 @@ func (d *BoltDb) GetTemplateVaults(projectID int, templateID int) (vaults []db.T
if err != nil {
return
}
for _, vault := range vaults {
err = db.FillTemplateVault(d, projectID, &vault)
for i := range vaults {
err = db.FillTemplateVault(d, projectID, &vaults[i])
if err != nil {
return
}
@ -40,39 +39,25 @@ func (d *BoltDb) UpdateTemplateVaults(projectID int, templateID int, vaults []db
var oldVaults []db.TemplateVault
oldVaults, err = d.GetTemplateVaults(projectID, templateID)
var vaultIDs []int
for _, vault := range vaults {
vault.ProjectID = projectID
vault.TemplateID = templateID
if vault.ID == 0 {
// Insert new vaults
var newTpl interface{}
newTpl, err = d.createObject(projectID, db.TemplateVaultProps, vault)
err = d.db.Update(func(tx *bbolt.Tx) error {
for _, vault := range oldVaults {
err = d.deleteObject(projectID, db.TemplateVaultProps, intObjectID(vault.ID), tx)
if err != nil {
return
return err
}
vaultIDs = append(vaultIDs, newTpl.(db.TemplateVault).ID)
} else {
// Update existing vaults
err = d.updateObject(projectID, db.TemplateVaultProps, vault)
vaultIDs = append(vaultIDs, vault.ID)
}
if err != nil {
return
}
}
// Delete missing vaults
for _, vault := range oldVaults {
if !slices.Contains(vaultIDs, vault.ID) {
err = d.db.Update(func(tx *bbolt.Tx) error {
return d.deleteObject(projectID, db.TemplateVaultProps, intObjectID(vault.ID), tx)
})
for _, vault := range vaults {
vault.ProjectID = projectID
vault.TemplateID = templateID
_, err = d.createObjectTx(tx, projectID, db.TemplateVaultProps, vault)
if err != nil {
return
return err
}
}
}
return nil
})
return
}

View File

@ -9,18 +9,15 @@ import (
func (d *SqlDb) GetTemplateVaults(projectID int, templateID int) (vaults []db.TemplateVault, err error) {
vaults = []db.TemplateVault{}
var vlts []db.TemplateVault
_, err = d.selectAll(&vlts, "select * from project__template_vault where project_id=? and template_id=?", projectID, templateID)
_, err = d.selectAll(&vaults, "select * from project__template_vault where project_id=? and template_id=?", projectID, templateID)
if err != nil {
return
}
for _, vault := range vlts {
vault := vault
err = db.FillTemplateVault(d, projectID, &vault)
for i := range vaults {
err = db.FillTemplateVault(d, projectID, &vaults[i])
if err != nil {
return
}
vaults = append(vaults, vault)
}
return
}

View File

@ -1,7 +1,9 @@
package project
import (
"encoding/json"
"fmt"
"reflect"
"github.com/ansible-semaphore/semaphore/db"
"github.com/ansible-semaphore/semaphore/pkg/random"
@ -156,21 +158,17 @@ func (b *BackupDB) new(projectID int, store db.Store) (*BackupDB, error) {
}
func (b *BackupDB) format() (*BackupFormat, error) {
keys := make([]BackupKey, len(b.keys))
keys := make([]BackupAccessKey, len(b.keys))
for i, o := range b.keys {
keys[i] = BackupKey{
Name: o.Name,
Type: o.Type,
keys[i] = BackupAccessKey{
o,
}
}
environments := make([]BackupEnvironment, len(b.environments))
for i, o := range b.environments {
environments[i] = BackupEnvironment{
Name: o.Name,
ENV: o.ENV,
JSON: o.JSON,
Password: o.Password,
o,
}
}
@ -185,9 +183,7 @@ func (b *BackupDB) format() (*BackupFormat, error) {
BecomeKey, _ = findNameByID[db.AccessKey](*o.BecomeKeyID, b.keys)
}
inventories[i] = BackupInventory{
Name: o.Name,
Inventory: o.Inventory,
Type: o.Type,
Inventory: o,
SSHKey: SSHKey,
BecomeKey: BecomeKey,
}
@ -196,8 +192,7 @@ func (b *BackupDB) format() (*BackupFormat, error) {
views := make([]BackupView, len(b.views))
for i, o := range b.views {
views[i] = BackupView{
Name: o.Title,
Position: o.Position,
o,
}
}
@ -205,10 +200,8 @@ func (b *BackupDB) format() (*BackupFormat, error) {
for i, o := range b.repositories {
SSHKey, _ := findNameByID[db.AccessKey](o.SSHKeyID, b.keys)
repositories[i] = BackupRepository{
Name: o.Name,
SSHKey: SSHKey,
GitURL: o.GitURL,
GitBranch: o.GitBranch,
Repository: o,
SSHKey: SSHKey,
}
}
@ -223,8 +216,8 @@ func (b *BackupDB) format() (*BackupFormat, error) {
var vaultKey *string = nil
vaultKey, _ = findNameByID[db.AccessKey](vault.VaultKeyID, b.keys)
vaults = append(vaults, BackupTemplateVault{
Name: vault.Name,
VaultKey: *vaultKey,
TemplateVault: vault,
VaultKey: *vaultKey,
})
}
@ -244,31 +237,19 @@ func (b *BackupDB) format() (*BackupFormat, error) {
}
templates[i] = BackupTemplate{
Name: o.Name,
AllowOverrideArgsInTask: o.AllowOverrideArgsInTask,
Arguments: o.Arguments,
Autorun: o.Autorun,
Description: o.Description,
Playbook: o.Playbook,
StartVersion: o.StartVersion,
SuppressSuccessAlerts: o.SuppressSuccessAlerts,
SurveyVars: o.SurveyVarsJSON,
Type: o.Type,
View: View,
Repository: *Repository,
Inventory: Inventory,
Environment: Environment,
BuildTemplate: BuildTemplate,
Cron: getScheduleByTemplate(o.ID, b.schedules),
Vaults: vaults,
Template: o,
View: View,
Repository: *Repository,
Inventory: Inventory,
Environment: Environment,
BuildTemplate: BuildTemplate,
Cron: getScheduleByTemplate(o.ID, b.schedules),
Vaults: vaults,
}
}
return &BackupFormat{
Meta: BackupMeta{
Name: b.meta.Name,
MaxParallelTasks: b.meta.MaxParallelTasks,
Alert: b.meta.Alert,
AlertChat: b.meta.AlertChat,
b.meta,
},
Inventories: inventories,
Environments: environments,
@ -287,3 +268,31 @@ func GetBackup(projectID int, store db.Store) (*BackupFormat, error) {
return backup.format()
}
func (b *BackupFormat) Marshal() (res string, err error) {
data, err := marshalValue(reflect.ValueOf(b))
if err != nil {
return
}
bytes, err := json.Marshal(data)
if err != nil {
return
}
res = string(bytes)
return
}
func (b *BackupFormat) Unmarshal(res string) (err error) {
// Parse the JSON data into a map
var jsonData interface{}
if err = json.Unmarshal([]byte(res), &jsonData); err != nil {
return
}
// Start the recursive unmarshaling process
err = unmarshalValueWithBackupTags(jsonData, reflect.ValueOf(b))
return
}

View File

@ -0,0 +1,293 @@
package project
import (
"fmt"
"reflect"
)
func marshalValue(v reflect.Value) (interface{}, error) {
// Handle pointers
if v.Kind() == reflect.Ptr {
if v.IsNil() {
return nil, nil
}
return marshalValue(v.Elem())
}
// Handle structs
if v.Kind() == reflect.Struct {
typeOfV := v.Type()
result := make(map[string]interface{})
for i := 0; i < v.NumField(); i++ {
fieldValue := v.Field(i)
fieldType := typeOfV.Field(i)
// Handle anonymous fields (embedded structs)
if fieldType.Anonymous {
embeddedValue, err := marshalValue(fieldValue)
if err != nil {
return nil, err
}
if embeddedMap, ok := embeddedValue.(map[string]interface{}); ok {
// Merge embedded struct fields into parent result map
for k, v := range embeddedMap {
result[k] = v
}
}
continue
}
tag := fieldType.Tag.Get("backup")
if tag == "-" {
continue // Skip fields with backup:"-"
}
// Check if the field should be backed up
if tag == "" {
// Get the field name from the "db" tag
tag = fieldType.Tag.Get("db")
if tag == "" || tag == "-" {
continue // Skip if "db" tag is empty or "-"
}
}
// Recursively process the field value
value, err := marshalValue(fieldValue)
if err != nil {
return nil, err
}
if value == nil {
continue
}
result[tag] = value
}
return result, nil
}
// Handle slices and arrays
if v.Kind() == reflect.Slice || v.Kind() == reflect.Array {
if v.IsNil() {
return make([]interface{}, 0), nil
}
var result []interface{} = make([]interface{}, 0)
for i := 0; i < v.Len(); i++ {
elemValue, err := marshalValue(v.Index(i))
if err != nil {
return nil, err
}
result = append(result, elemValue)
}
return result, nil
}
// Handle maps
if v.Kind() == reflect.Map {
if v.IsNil() {
return make(map[string]interface{}), nil
}
result := make(map[string]interface{})
for _, key := range v.MapKeys() {
// Assuming the key is a string
mapKey := fmt.Sprintf("%v", key.Interface())
mapValue, err := marshalValue(v.MapIndex(key))
if err != nil {
return nil, err
}
result[mapKey] = mapValue
}
return result, nil
}
// Handle other types (int, string, etc.)
return v.Interface(), nil
}
func setBasicType(data interface{}, v reflect.Value) error {
if !v.CanSet() {
return fmt.Errorf("cannot set value of type %v", v.Type())
}
switch v.Kind() {
case reflect.Bool:
b, ok := data.(bool)
if !ok {
return fmt.Errorf("expected bool for field, got %T", data)
}
v.SetBool(b)
case reflect.String:
s, ok := data.(string)
if !ok {
return fmt.Errorf("expected string for field, got %T", data)
}
v.SetString(s)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
n, ok := toFloat64(data)
if !ok {
return fmt.Errorf("expected number for field, got %T", data)
}
v.SetInt(int64(n))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
n, ok := toFloat64(data)
if !ok {
return fmt.Errorf("expected number for field, got %T", data)
}
v.SetUint(uint64(n))
case reflect.Float32, reflect.Float64:
n, ok := toFloat64(data)
if !ok {
return fmt.Errorf("expected number for field, got %T", data)
}
v.SetFloat(n)
default:
return fmt.Errorf("unsupported kind %v", v.Kind())
}
return nil
}
func toFloat64(data interface{}) (float64, bool) {
switch n := data.(type) {
case float64:
return n, true
case float32:
return float64(n), true
case int:
return float64(n), true
case int64:
return float64(n), true
case int32:
return float64(n), true
case int16:
return float64(n), true
case int8:
return float64(n), true
case uint:
return float64(n), true
case uint64:
return float64(n), true
case uint32:
return float64(n), true
case uint16:
return float64(n), true
case uint8:
return float64(n), true
default:
return 0, false
}
}
func unmarshalValueWithBackupTags(data interface{}, v reflect.Value) error {
// Handle pointers
if v.Kind() == reflect.Ptr {
// Initialize pointer if it's nil
if v.IsNil() {
v.Set(reflect.New(v.Type().Elem()))
}
return unmarshalValueWithBackupTags(data, v.Elem())
}
// Handle structs
if v.Kind() == reflect.Struct {
// Data should be a map
m, ok := data.(map[string]interface{})
if !ok {
return fmt.Errorf("expected object for struct, got %T", data)
}
return unmarshalStructWithBackupTags(m, v)
}
// Handle slices and arrays
if v.Kind() == reflect.Slice {
dataSlice, ok := data.([]interface{})
if !ok {
return fmt.Errorf("expected array for slice, got %T", data)
}
slice := reflect.MakeSlice(v.Type(), len(dataSlice), len(dataSlice))
for i := 0; i < len(dataSlice); i++ {
elem := slice.Index(i)
if err := unmarshalValueWithBackupTags(dataSlice[i], elem); err != nil {
return err
}
}
v.Set(slice)
return nil
}
// Handle maps
if v.Kind() == reflect.Map {
dataMap, ok := data.(map[string]interface{})
if !ok {
return fmt.Errorf("expected object for map, got %T", data)
}
mapType := v.Type()
mapValue := reflect.MakeMap(mapType)
for key, value := range dataMap {
keyVal := reflect.ValueOf(key).Convert(mapType.Key())
valVal := reflect.New(mapType.Elem()).Elem()
if err := unmarshalValueWithBackupTags(value, valVal); err != nil {
return err
}
mapValue.SetMapIndex(keyVal, valVal)
}
v.Set(mapValue)
return nil
}
// Handle basic types
return setBasicType(data, v)
}
func unmarshalStructWithBackupTags(data map[string]interface{}, v reflect.Value) error {
t := v.Type()
for i := 0; i < v.NumField(); i++ {
fieldType := t.Field(i)
fieldValue := v.Field(i)
// Handle anonymous fields (embedded structs)
if fieldType.Anonymous {
// Pass the entire data map to the embedded struct
if err := unmarshalStructWithBackupTags(data, fieldValue); err != nil {
return err
}
continue
}
// Skip fields with backup:"-"
backupTag := fieldType.Tag.Get("backup")
if backupTag == "-" {
continue
}
// Determine the JSON key to use
var jsonKey string
if backupTag != "" {
jsonKey = backupTag
} else {
dbTag := fieldType.Tag.Get("db")
if dbTag != "" && dbTag != "-" {
jsonKey = dbTag
} else {
continue // Skip if no backup or db tag
}
}
// Check if the key exists in the data
if value, ok := data[jsonKey]; ok {
if !fieldValue.CanSet() {
continue // Skip fields that cannot be set
}
if value == nil {
continue
}
if err := unmarshalValueWithBackupTags(value, fieldValue); err != nil {
return err
}
}
}
return nil
}

View File

@ -1,6 +1,9 @@
package project
import (
"github.com/ansible-semaphore/semaphore/db"
"github.com/ansible-semaphore/semaphore/db/bolt"
"github.com/ansible-semaphore/semaphore/util"
"testing"
)
@ -8,6 +11,120 @@ type testItem struct {
Name string
}
func TestBackupProject(t *testing.T) {
util.Config = &util.ConfigType{
TmpPath: "/tmp",
}
store := bolt.CreateTestStore()
proj, err := store.CreateProject(db.Project{
Name: "Test 123",
})
if err != nil {
t.Fatal(err)
}
key, err := store.CreateAccessKey(db.AccessKey{
ProjectID: &proj.ID,
Type: db.AccessKeyNone,
})
if err != nil {
t.Fatal(err)
}
repo, err := store.CreateRepository(db.Repository{
ProjectID: proj.ID,
SSHKeyID: key.ID,
Name: "Test",
GitURL: "git@example.com:test/test",
GitBranch: "master",
})
if err != nil {
t.Fatal(err)
}
inv, err := store.CreateInventory(db.Inventory{
ProjectID: proj.ID,
ID: 1,
})
if err != nil {
t.Fatal(err)
}
env, err := store.CreateEnvironment(db.Environment{
ProjectID: proj.ID,
Name: "test",
JSON: `{"author": "Denis", "comment": "Hello, World!"}`,
})
if err != nil {
t.Fatal(err)
}
_, err = store.CreateTemplate(db.Template{
Name: "Test",
Playbook: "test.yml",
ProjectID: proj.ID,
RepositoryID: repo.ID,
InventoryID: &inv.ID,
EnvironmentID: &env.ID,
})
if err != nil {
t.Fatal(err)
}
backup, err := GetBackup(proj.ID, store)
if err != nil {
t.Fatal(err)
}
if backup.Meta.ID != proj.ID {
t.Fatal("backup meta ID wrong")
}
str, err := backup.Marshal()
if err != nil {
t.Fatal(err)
}
if str != `{"environments":[{"json":"{\"author\": \"Denis\", \"comment\": \"Hello, World!\"}","name":"test"}],"inventories":[{"inventory":"","name":"","type":""}],"keys":[{"name":"","type":"none"}],"meta":{"alert":false,"max_parallel_tasks":0,"name":"Test 123","type":""},"repositories":[{"git_branch":"master","git_url":"git@example.com:test/test","name":"Test","ssh_key":""}],"templates":[{"allow_override_args_in_task":false,"app":"","autorun":false,"environment":"test","inventory":"","name":"Test","playbook":"test.yml","repository":"Test","suppress_success_alerts":false,"type":"","vaults":[]}],"views":[]}` {
t.Fatal("Invalid backup content")
}
restoredBackup := &BackupFormat{}
err = restoredBackup.Unmarshal(str)
if err != nil {
t.Fatal(err)
}
if restoredBackup.Meta.Name != proj.Name {
t.Fatal("backup meta ID wrong")
}
user, err := store.CreateUser(db.UserWithPwd{
Pwd: "3412341234123",
User: db.User{
Username: "test",
Name: "Test",
Email: "test@example.com",
Admin: true,
},
})
if err != nil {
t.Fatal(err)
}
restoredProj, err := restoredBackup.Restore(user, store)
if err != nil {
t.Fatal(err)
}
if restoredProj.Name != proj.Name {
t.Fatal("backup meta ID wrong")
}
}
func isUnique(items []testItem) bool {
for i, item := range items {
for k, other := range items {

View File

@ -37,57 +37,46 @@ func (e BackupEnvironment) Verify(backup *BackupFormat) error {
}
func (e BackupEnvironment) Restore(store db.Store, b *BackupDB) error {
environment, err := store.CreateEnvironment(
db.Environment{
Name: e.Name,
Password: e.Password,
ProjectID: b.meta.ID,
JSON: e.JSON,
ENV: e.ENV,
},
)
env := e.Environment
env.ProjectID = b.meta.ID
newEnv, err := store.CreateEnvironment(env)
if err != nil {
return err
}
b.environments = append(b.environments, environment)
b.environments = append(b.environments, newEnv)
return nil
}
func (e BackupView) Verify(backup *BackupFormat) error {
return verifyDuplicate[BackupView](e.Name, backup.Views)
return verifyDuplicate[BackupView](e.Title, backup.Views)
}
func (e BackupView) Restore(store db.Store, b *BackupDB) error {
view, err := store.CreateView(
db.View{
Title: e.Name,
ProjectID: b.meta.ID,
Position: e.Position,
},
)
v := e.View
v.ProjectID = b.meta.ID
newView, err := store.CreateView(v)
if err != nil {
return err
}
b.views = append(b.views, view)
b.views = append(b.views, newView)
return nil
}
func (e BackupKey) Verify(backup *BackupFormat) error {
return verifyDuplicate[BackupKey](e.Name, backup.Keys)
func (e BackupAccessKey) Verify(backup *BackupFormat) error {
return verifyDuplicate[BackupAccessKey](e.Name, backup.Keys)
}
func (e BackupKey) Restore(store db.Store, b *BackupDB) error {
key, err := store.CreateAccessKey(
db.AccessKey{
Name: e.Name,
ProjectID: &b.meta.ID,
Type: e.Type,
},
)
func (e BackupAccessKey) Restore(store db.Store, b *BackupDB) error {
key := e.AccessKey
key.ProjectID = &b.meta.ID
newKey, err := store.CreateAccessKey(key)
if err != nil {
return err
}
b.keys = append(b.keys, key)
b.keys = append(b.keys, newKey)
return nil
}
@ -95,10 +84,10 @@ func (e BackupInventory) Verify(backup *BackupFormat) error {
if err := verifyDuplicate[BackupInventory](e.Name, backup.Inventories); err != nil {
return err
}
if e.SSHKey != nil && getEntryByName[BackupKey](e.SSHKey, backup.Keys) == nil {
if e.SSHKey != nil && getEntryByName[BackupAccessKey](e.SSHKey, backup.Keys) == nil {
return fmt.Errorf("SSHKey does not exist in keys[].Name")
}
if e.BecomeKey != nil && getEntryByName[BackupKey](e.BecomeKey, backup.Keys) == nil {
if e.BecomeKey != nil && getEntryByName[BackupAccessKey](e.BecomeKey, backup.Keys) == nil {
return fmt.Errorf("BecomeKey does not exist in keys[].Name")
}
return nil
@ -121,20 +110,17 @@ func (e BackupInventory) Restore(store db.Store, b *BackupDB) error {
} else {
BecomeKeyID = &((*k).ID)
}
inventory, err := store.CreateInventory(
db.Inventory{
ProjectID: b.meta.ID,
Name: e.Name,
Type: e.Type,
SSHKeyID: SSHKeyID,
BecomeKeyID: BecomeKeyID,
Inventory: e.Inventory,
},
)
inv := e.Inventory
inv.ProjectID = b.meta.ID
inv.SSHKeyID = SSHKeyID
inv.BecomeKeyID = BecomeKeyID
newInventory, err := store.CreateInventory(inv)
if err != nil {
return err
}
b.inventories = append(b.inventories, inventory)
b.inventories = append(b.inventories, newInventory)
return nil
}
@ -142,7 +128,7 @@ func (e BackupRepository) Verify(backup *BackupFormat) error {
if err := verifyDuplicate[BackupRepository](e.Name, backup.Repositories); err != nil {
return err
}
if e.SSHKey != nil && getEntryByName[BackupKey](e.SSHKey, backup.Keys) == nil {
if e.SSHKey != nil && getEntryByName[BackupAccessKey](e.SSHKey, backup.Keys) == nil {
return fmt.Errorf("SSHKey does not exist in keys[].Name")
}
return nil
@ -155,19 +141,16 @@ func (e BackupRepository) Restore(store db.Store, b *BackupDB) error {
} else {
SSHKeyID = (*k).ID
}
repository, err := store.CreateRepository(
db.Repository{
ProjectID: b.meta.ID,
Name: e.Name,
GitBranch: e.GitBranch,
GitURL: e.GitURL,
SSHKeyID: SSHKeyID,
},
)
repo := e.Repository
repo.ProjectID = b.meta.ID
repo.SSHKeyID = SSHKeyID
newRepo, err := store.CreateRepository(repo)
if err != nil {
return err
}
b.repositories = append(b.repositories, repository)
b.repositories = append(b.repositories, newRepo)
return nil
}
@ -175,31 +158,39 @@ func (e BackupTemplate) Verify(backup *BackupFormat) error {
if err := verifyDuplicate[BackupTemplate](e.Name, backup.Templates); err != nil {
return err
}
if getEntryByName[BackupRepository](&e.Repository, backup.Repositories) == nil {
return fmt.Errorf("repository does not exist in repositories[].name")
}
if getEntryByName[BackupInventory](e.Inventory, backup.Inventories) == nil {
if e.Inventory != nil && getEntryByName[BackupInventory](e.Inventory, backup.Inventories) == nil {
return fmt.Errorf("inventory does not exist in inventories[].name")
}
if e.VaultKey != nil && getEntryByName[BackupKey](e.VaultKey, backup.Keys) == nil {
if e.VaultKey != nil && getEntryByName[BackupAccessKey](e.VaultKey, backup.Keys) == nil {
return fmt.Errorf("vault_key does not exist in keys[].name")
}
if e.Vaults != nil {
for _, vault := range e.Vaults {
if getEntryByName[BackupKey](&vault.VaultKey, backup.Keys) == nil {
if getEntryByName[BackupAccessKey](&vault.VaultKey, backup.Keys) == nil {
return fmt.Errorf("vaults[].vaultKey does not exist in keys[].name")
}
}
}
if e.View != nil && getEntryByName[BackupView](e.View, backup.Views) == nil {
return fmt.Errorf("view does not exist in views[].name")
}
if string(e.Type) == "deploy" && e.BuildTemplate == nil {
return fmt.Errorf("type is deploy but build_template is null")
}
if string(e.Type) != "deploy" && e.BuildTemplate != nil {
return fmt.Errorf("type is not deploy but build_template is not null")
}
if buildTemplate := getEntryByName[BackupTemplate](e.BuildTemplate, backup.Templates); string(e.Type) == "deploy" && buildTemplate == nil {
return fmt.Errorf("deploy is build but build_template does not exist in templates[].name")
}
@ -214,24 +205,33 @@ func (e BackupTemplate) Verify(backup *BackupFormat) error {
}
func (e BackupTemplate) Restore(store db.Store, b *BackupDB) error {
var InventoryID int
if k := findEntityByName[db.Inventory](e.Inventory, b.inventories); k == nil {
return fmt.Errorf("inventory does not exist in inventories[].name")
} else {
InventoryID = k.GetID()
var InventoryID *int
if e.Inventory != nil {
if k := findEntityByName[db.Inventory](e.Inventory, b.inventories); k == nil {
return fmt.Errorf("inventory does not exist in inventories[].name")
} else {
id := k.GetID()
InventoryID = &id
}
}
var EnvironmentID int
if k := findEntityByName[db.Environment](e.Environment, b.environments); k == nil {
return fmt.Errorf("environment does not exist in environments[].name")
} else {
EnvironmentID = k.GetID()
var EnvironmentID *int
if e.Environment != nil {
if k := findEntityByName[db.Environment](e.Environment, b.environments); k == nil {
return fmt.Errorf("environment does not exist in environments[].name")
} else {
id := k.GetID()
EnvironmentID = &id
}
}
var RepositoryID int
if k := findEntityByName[db.Repository](&e.Repository, b.repositories); k == nil {
return fmt.Errorf("repository does not exist in repositories[].name")
} else {
RepositoryID = k.GetID()
}
var BuildTemplateID *int
if string(e.Type) != "deploy" {
BuildTemplateID = nil
@ -240,38 +240,32 @@ func (e BackupTemplate) Restore(store db.Store, b *BackupDB) error {
} else {
BuildTemplateID = &(k.ID)
}
var ViewID *int
if k := findEntityByName[db.View](e.View, b.views); k == nil {
ViewID = nil
} else {
ViewID = &k.ID
}
template, err := store.CreateTemplate(
db.Template{
ProjectID: b.meta.ID,
InventoryID: &InventoryID,
EnvironmentID: &EnvironmentID,
RepositoryID: RepositoryID,
ViewID: ViewID,
Autorun: e.Autorun,
AllowOverrideArgsInTask: e.AllowOverrideArgsInTask,
SuppressSuccessAlerts: e.SuppressSuccessAlerts,
Name: e.Name,
Playbook: e.Playbook,
Arguments: e.Arguments,
Type: e.Type,
BuildTemplateID: BuildTemplateID,
},
)
template := e.Template
template.ProjectID = b.meta.ID
template.RepositoryID = RepositoryID
template.EnvironmentID = EnvironmentID
template.InventoryID = InventoryID
template.ViewID = ViewID
template.BuildTemplateID = BuildTemplateID
newTemplate, err := store.CreateTemplate(template)
if err != nil {
return err
}
b.templates = append(b.templates, template)
b.templates = append(b.templates, newTemplate)
if e.Cron != nil {
_, err := store.CreateSchedule(
db.Schedule{
ProjectID: b.meta.ID,
TemplateID: template.ID,
TemplateID: newTemplate.ID,
CronFormat: *e.Cron,
RepositoryID: &RepositoryID,
},
@ -288,14 +282,13 @@ func (e BackupTemplate) Restore(store db.Store, b *BackupDB) error {
} else {
VaultKeyID = k.ID
}
_, err := store.CreateTemplateVault(
db.TemplateVault{
ProjectID: b.meta.ID,
TemplateID: template.ID,
VaultKeyID: VaultKeyID,
Name: vault.Name,
},
)
tplVault := vault.TemplateVault
tplVault.ProjectID = b.meta.ID
tplVault.TemplateID = newTemplate.ID
tplVault.VaultKeyID = VaultKeyID
_, err := store.CreateTemplateVault(tplVault)
if err != nil {
return err
}
@ -340,43 +333,54 @@ func (backup *BackupFormat) Verify() error {
func (backup *BackupFormat) Restore(user db.User, store db.Store) (*db.Project, error) {
var b = BackupDB{}
project, err := store.CreateProject(
db.Project{
Name: backup.Meta.Name,
Alert: backup.Meta.Alert,
MaxParallelTasks: backup.Meta.MaxParallelTasks,
AlertChat: backup.Meta.AlertChat,
},
)
project := backup.Meta.Project
newProject, err := store.CreateProject(project)
if err != nil {
return nil, err
}
b.meta = project
if _, err = store.CreateProjectUser(db.ProjectUser{
ProjectID: newProject.ID,
UserID: user.ID,
Role: db.ProjectOwner,
}); err != nil {
return nil, err
}
b.meta = newProject
for i, o := range backup.Environments {
if err := o.Restore(store, &b); err != nil {
return nil, fmt.Errorf("error at environments[%d]: %s", i, err.Error())
}
}
for i, o := range backup.Views {
if err := o.Restore(store, &b); err != nil {
return nil, fmt.Errorf("error at views[%d]: %s", i, err.Error())
}
}
for i, o := range backup.Keys {
if err := o.Restore(store, &b); err != nil {
return nil, fmt.Errorf("error at keys[%d]: %s", i, err.Error())
}
}
for i, o := range backup.Repositories {
if err := o.Restore(store, &b); err != nil {
return nil, fmt.Errorf("error at repositories[%d]: %s", i, err.Error())
}
}
for i, o := range backup.Inventories {
if err := o.Restore(store, &b); err != nil {
return nil, fmt.Errorf("error at inventories[%d]: %s", i, err.Error())
}
}
deployTemplates := make([]int, 0)
for i, o := range backup.Templates {
if string(o.Type) == "deploy" {
@ -387,6 +391,7 @@ func (backup *BackupFormat) Restore(user db.User, store db.Store) (*db.Project,
return nil, fmt.Errorf("error at templates[%d]: %s", i, err.Error())
}
}
for _, i := range deployTemplates {
o := backup.Templates[i]
if err := o.Restore(store, &b); err != nil {
@ -394,13 +399,5 @@ func (backup *BackupFormat) Restore(user db.User, store db.Store) (*db.Project,
}
}
if _, err = store.CreateProjectUser(db.ProjectUser{
ProjectID: project.ID,
UserID: user.ID,
Role: db.ProjectOwner,
}); err != nil {
return nil, err
}
return &project, nil
return &newProject, nil
}

View File

@ -16,80 +16,60 @@ type BackupDB struct {
}
type BackupFormat struct {
Meta BackupMeta `json:"meta"`
Templates []BackupTemplate `json:"templates"`
Repositories []BackupRepository `json:"repositories"`
Keys []BackupKey `json:"keys"`
Views []BackupView `json:"views"`
Inventories []BackupInventory `json:"inventories"`
Environments []BackupEnvironment `json:"environments"`
Meta BackupMeta `backup:"meta"`
Templates []BackupTemplate `backup:"templates"`
Repositories []BackupRepository `backup:"repositories"`
Keys []BackupAccessKey `backup:"keys"`
Views []BackupView `backup:"views"`
Inventories []BackupInventory `backup:"inventories"`
Environments []BackupEnvironment `backup:"environments"`
}
type BackupMeta struct {
Name string `json:"name"`
Alert bool `json:"alert"`
AlertChat *string `json:"alert_chat"`
MaxParallelTasks int `json:"max_parallel_tasks"`
db.Project
}
type BackupEnvironment struct {
Name string `json:"name"`
Password *string `json:"password"`
JSON string `json:"json"`
ENV *string `json:"env"`
db.Environment
}
type BackupKey struct {
Name string `json:"name"`
Type db.AccessKeyType `json:"type"`
type BackupAccessKey struct {
db.AccessKey
}
type BackupView struct {
Name string `json:"name"`
Position int `json:"position"`
db.View
}
type BackupInventory struct {
Name string `json:"name"`
Inventory string `json:"inventory"`
SSHKey *string `json:"ssh_key"`
BecomeKey *string `json:"become_key"`
Type db.InventoryType `json:"type"`
db.Inventory
SSHKey *string `backup:"ssh_key"`
BecomeKey *string `backup:"become_key"`
}
type BackupRepository struct {
Name string `json:"name"`
GitURL string `json:"git_url"`
GitBranch string `json:"git_branch"`
SSHKey *string `json:"ssh_key"`
db.Repository
SSHKey *string `backup:"ssh_key"`
}
type BackupTemplate struct {
Inventory *string `json:"inventory"`
Repository string `json:"repository"`
Environment *string `json:"environment"`
Name string `json:"name"`
Playbook string `json:"playbook"`
Arguments *string `json:"arguments"`
AllowOverrideArgsInTask bool `json:"allow_override_args_in_task"`
Description *string `json:"description"`
Type db.TemplateType `json:"type"`
StartVersion *string `json:"start_version"`
BuildTemplate *string `json:"build_template"`
View *string `json:"view"`
Autorun bool `json:"autorun"`
SurveyVars *string `json:"survey_vars"`
SuppressSuccessAlerts bool `json:"suppress_success_alerts"`
Cron *string `json:"cron"`
Vaults []BackupTemplateVault `json:"vaults"`
db.Template
Inventory *string `backup:"inventory"`
Repository string `backup:"repository"`
Environment *string `backup:"environment"`
BuildTemplate *string `backup:"build_template"`
View *string `backup:"view"`
Vaults []BackupTemplateVault `backup:"vaults"`
Cron *string `backup:"cron"`
// Deprecated: Left here for compatibility with old backups
VaultKey *string `json:"vault_key"`
}
type BackupTemplateVault struct {
Name *string `json:"name"`
VaultKey string `json:"vault_key"`
db.TemplateVault
VaultKey string `backup:"vault_key"`
}
type BackupEntry interface {
@ -106,7 +86,7 @@ func (e BackupInventory) GetName() string {
return e.Name
}
func (e BackupKey) GetName() string {
func (e BackupAccessKey) GetName() string {
return e.Name
}
@ -115,7 +95,7 @@ func (e BackupRepository) GetName() string {
}
func (e BackupView) GetName() string {
return e.Name
return e.Title
}
func (e BackupTemplate) GetName() string {

View File

@ -30,9 +30,8 @@ type LocalJob struct {
// Internal field
Process *os.Process
sshKeyInstallation db.AccessKeyInstallation
becomeKeyInstallation db.AccessKeyInstallation
sshKeyInstallation db.AccessKeyInstallation
becomeKeyInstallation db.AccessKeyInstallation
vaultFileInstallations map[string]db.AccessKeyInstallation
}

View File

@ -167,7 +167,7 @@ func (t *TaskRunner) run() {
t.SetStatus(task_logger.TaskSuccessStatus)
}
templates, err := t.pool.store.GetTemplates(t.Task.ProjectID, db.TemplateFilter{
tpls, err := t.pool.store.GetTemplates(t.Task.ProjectID, db.TemplateFilter{
BuildTemplateID: &t.Task.TemplateID,
AutorunOnly: true,
}, db.RetrieveQueryParams{})
@ -176,7 +176,7 @@ func (t *TaskRunner) run() {
return
}
for _, tpl := range templates {
for _, tpl := range tpls {
_, err = t.pool.AddTask(db.Task{
TemplateID: tpl.ID,
ProjectID: tpl.ProjectID,

View File

@ -31,7 +31,7 @@ export default {
editUser: 'Edit User',
newProject: 'New Project',
close: 'Close',
newProject2: 'New project...',
newProject2: 'New Project...',
demoMode: 'DEMO MODE',
task: 'Task #{expr}',
youCanRunAnyTasks: 'You can run any tasks',