mirror of
https://github.com/semaphoreui/semaphore.git
synced 2024-11-23 12:30:41 +01:00
Merge pull request #2395 from semaphoreui/refactor_backups
refactor backups
This commit is contained in:
commit
c7aa45516d
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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"`
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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:"-"`
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
293
services/project/backup_marshal.go
Normal file
293
services/project/backup_marshal.go
Normal 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
|
||||
}
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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',
|
||||
|
Loading…
Reference in New Issue
Block a user