From 6970336d874b303e83de728bfcb2446a8b455bce Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Wed, 28 Sep 2022 08:38:34 -0700 Subject: [PATCH] Update model definitions and saving --- internal/server/singleprocess/state/basis.go | 29 +++---- internal/server/singleprocess/state/box.go | 10 ++- internal/server/singleprocess/state/job.go | 20 ++--- .../server/singleprocess/state/project.go | 62 +++++++++------ internal/server/singleprocess/state/runner.go | 5 +- internal/server/singleprocess/state/target.go | 78 +++++++++---------- .../server/singleprocess/state/vagrantfile.go | 55 ++++--------- 7 files changed, 120 insertions(+), 139 deletions(-) diff --git a/internal/server/singleprocess/state/basis.go b/internal/server/singleprocess/state/basis.go index 1b18fead9..424f5655f 100644 --- a/internal/server/singleprocess/state/basis.go +++ b/internal/server/singleprocess/state/basis.go @@ -21,18 +21,18 @@ type scope interface { } type Basis struct { - gorm.Model + Model - Vagrantfile *Vagrantfile `mapstructure:"-"` - VagrantfileID uint `mapstructure:"-"` + Vagrantfile *Vagrantfile `mapstructure:"Configuration" gorm:"OnDelete:Cascade"` + VagrantfileID *uint `mapstructure:"-"` DataSource *ProtoValue - Jobs []*InternalJob `gorm:"polymorphic:Scope;" mapstructure:"-"` + Jobs []*InternalJob `gorm:"polymorphic:Scope" mapstructure:"-"` Metadata MetadataSet Name *string `gorm:"uniqueIndex,not null"` Path *string `gorm:"uniqueIndex,not null"` Projects []*Project RemoteEnabled bool - ResourceId *string `gorm:"<-:create;uniqueIndex;not null"` + ResourceId *string `gorm:"<-:create,uniqueIndex,not null"` } func (b *Basis) scope() interface{} { @@ -65,7 +65,7 @@ func (b *Basis) Validate(tx *gorm.DB) error { checkUnique( tx.Model(&Basis{}). Where(&Basis{Name: b.Name}). - Not(&Basis{Model: gorm.Model{ID: b.ID}}), + Not(&Basis{Model: Model{ID: b.ID}}), ), ), ), @@ -75,7 +75,7 @@ func (b *Basis) Validate(tx *gorm.DB) error { checkUnique( tx.Model(&Basis{}). Where(&Basis{Path: b.Path}). - Not(&Basis{Model: gorm.Model{ID: b.ID}}), + Not(&Basis{Model: Model{ID: b.ID}}), ), ), ), @@ -85,7 +85,7 @@ func (b *Basis) Validate(tx *gorm.DB) error { checkUnique( tx.Model(&Basis{}). Where(&Basis{ResourceId: b.ResourceId}). - Not(&Basis{Model: gorm.Model{ID: b.ID}}), + Not(&Basis{Model: Model{ID: b.ID}}), ), ), ), @@ -285,17 +285,8 @@ func (s *State) BasisPut( return nil, saveErrorToStatus("basis", err) } - if b.Configuration != nil { - if basis.Vagrantfile != nil { - basis.Vagrantfile.UpdateFromProto(b.Configuration) - } else { - basis.Vagrantfile = s.VagrantfileFromProto(b.Configuration) - } - } - - result := s.db.Save(basis) - if result.Error != nil { - return nil, saveErrorToStatus("basis", result.Error) + if err := s.upsertFull(basis); err != nil { + return nil, saveErrorToStatus("basis", err) } return basis.ToProto(), nil diff --git a/internal/server/singleprocess/state/box.go b/internal/server/singleprocess/state/box.go index a01516167..5be6b8642 100644 --- a/internal/server/singleprocess/state/box.go +++ b/internal/server/singleprocess/state/box.go @@ -262,9 +262,8 @@ func (s *State) BoxPut(b *vagrant_server.Box) error { return saveErrorToStatus("box", err) } - result := s.db.Save(box) - if result.Error != nil { - return saveErrorToStatus("box", result.Error) + if err := s.upsertFull(box); err != nil { + return saveErrorToStatus("box", err) } return nil @@ -304,6 +303,9 @@ func (s *State) BoxFind( if _, err := version.NewVersion(b.Version); err == nil { box, err := s.BoxFromProtoRefFuzzy(b) if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, nil + } return nil, lookupErrorToStatus("box", err) } @@ -360,5 +362,5 @@ func (s *State) BoxFind( return match.ToProto(), nil } - return nil, lookupErrorToStatus("box", gorm.ErrRecordNotFound) + return nil, nil // lookupErrorToStatus("box", gorm.ErrRecordNotFound) } diff --git a/internal/server/singleprocess/state/job.go b/internal/server/singleprocess/state/job.go index 964285e59..a8537b87e 100644 --- a/internal/server/singleprocess/state/job.go +++ b/internal/server/singleprocess/state/job.go @@ -57,7 +57,7 @@ type InternalJob struct { AssignTime *time.Time AckTime *time.Time - AssignedRunnerID uint `mapstructure:"-"` + AssignedRunnerID *uint `mapstructure:"-"` AssignedRunner *Runner CancelTime *time.Time CompleteTime *time.Time @@ -71,7 +71,7 @@ type InternalJob struct { QueueTime *time.Time Result *ProtoValue Scope scope `gorm:"-:all"` - ScopeID uint `mapstructure:"-"` + ScopeID *uint `mapstructure:"-"` ScopeType string `mapstructure:"-"` State JobState TargetRunner *ProtoValue @@ -94,19 +94,19 @@ func (i *InternalJob) BeforeCreate(tx *gorm.DB) error { // If the job has a scope assigned to it, persist it. func (i *InternalJob) BeforeSave(tx *gorm.DB) (err error) { if i.Scope == nil { - i.ScopeID = 0 + i.ScopeID = nil i.ScopeType = "" return nil } switch v := i.Scope.(type) { case *Basis: - i.ScopeID = v.ID + i.ScopeID = &v.ID i.ScopeType = "basis" case *Project: - i.ScopeID = v.ID + i.ScopeID = &v.ID i.ScopeType = "project" case *Target: - i.ScopeID = v.ID + i.ScopeID = &v.ID i.ScopeType = "target" default: return fmt.Errorf("unknown scope type (%T)", i.Scope) @@ -117,14 +117,14 @@ func (i *InternalJob) BeforeSave(tx *gorm.DB) (err error) { // If the job has a scope, load it. func (i *InternalJob) AfterFind(tx *gorm.DB) (err error) { - if i.ScopeID == 0 { + if i.ScopeID == nil { return nil } switch i.ScopeType { case "basis": var b Basis result := tx.Preload(clause.Associations). - First(&b, &Basis{Model: Model{ID: i.ScopeID}}) + First(&b, &Basis{Model: Model{ID: *i.ScopeID}}) if result.Error != nil { return result.Error } @@ -132,7 +132,7 @@ func (i *InternalJob) AfterFind(tx *gorm.DB) (err error) { case "project": var p Project result := tx.Preload(clause.Associations). - First(&p, &Project{Model: Model{ID: i.ScopeID}}) + First(&p, &Project{Model: Model{ID: *i.ScopeID}}) if result.Error != nil { return result.Error } @@ -140,7 +140,7 @@ func (i *InternalJob) AfterFind(tx *gorm.DB) (err error) { case "target": var t Target result := tx.Preload(clause.Associations). - First(&t, &Target{Model: Model{ID: i.ScopeID}}) + First(&t, &Target{Model: Model{ID: *i.ScopeID}}) if result.Error != nil { return result.Error } diff --git a/internal/server/singleprocess/state/project.go b/internal/server/singleprocess/state/project.go index c9f83c1f1..ad88ef5c3 100644 --- a/internal/server/singleprocess/state/project.go +++ b/internal/server/singleprocess/state/project.go @@ -15,20 +15,20 @@ func init() { } type Project struct { - gorm.Model + Model Basis *Basis - BasisID *uint `gorm:"uniqueIndex:idx_bname;not null" mapstructure:"-"` - Vagrantfile *Vagrantfile `mapstructure:"-"` + BasisID uint `gorm:"uniqueIndex:idx_bname" mapstructure:"-"` + Vagrantfile *Vagrantfile `gorm:"OnDelete:Cascade" mapstructure:"Configuration"` VagrantfileID *uint `mapstructure:"-"` DataSource *ProtoValue - Jobs []*InternalJob `gorm:"polymorphic:Scope;"` + Jobs []*InternalJob `gorm:"polymorphic:Scope"` Metadata MetadataSet - Name *string `gorm:"uniqueIndex:idx_bname;not null"` - Path *string `gorm:"uniqueIndex;not null"` + Name *string `gorm:"uniqueIndex:idx_bname,not null"` + Path *string `gorm:"uniqueIndex,not null"` RemoteEnabled bool - ResourceId *string `gorm:"<-:create;uniqueIndex;not null"` - Targets []*Target + ResourceId *string `gorm:"<-:create,uniqueIndex,not null"` + Targets []*Target `gorm:"OnDelete:Cascade"` } func (p *Project) scope() interface{} { @@ -49,16 +49,39 @@ func (p *Project) BeforeSave(tx *gorm.DB) error { return nil } +func (p *Project) BeforeUpdate(tx *gorm.DB) error { + // If a Vagrantfile was already set for the project, just update it + if p.Vagrantfile != nil && p.Vagrantfile.ID == 0 && p.VagrantfileID != nil { + var v Vagrantfile + result := tx.First(&v, &Vagrantfile{Model: Model{ID: *p.VagrantfileID}}) + if result.Error != nil { + return result.Error + } + id := v.ID + if err := decode(p, &v); err != nil { + return err + } + v.ID = id + p.Vagrantfile = &v + } + return nil +} + func (p *Project) Validate(tx *gorm.DB) error { err := validation.ValidateStruct(p, - // validation.Field(&p.Basis, validation.Required), + validation.Field(&p.BasisID, + validation.Required.When(p.Basis == nil), + ), + validation.Field(&p.Basis, + validation.Required.When(p.BasisID == 0), + ), validation.Field(&p.Name, validation.Required, validation.By( checkUnique( tx.Model(&Project{}). Where(&Project{Name: p.Name, BasisID: p.BasisID}). - Not(&Project{Model: gorm.Model{ID: p.ID}}), + Not(&Project{Model: Model{ID: p.ID}}), ), ), ), @@ -68,7 +91,7 @@ func (p *Project) Validate(tx *gorm.DB) error { checkUnique( tx.Model(&Project{}). Where(&Project{Path: p.Path, BasisID: p.BasisID}). - Not(&Project{Model: gorm.Model{ID: p.ID}}), + Not(&Project{Model: Model{ID: p.ID}}), ), ), ), @@ -78,7 +101,7 @@ func (p *Project) Validate(tx *gorm.DB) error { checkUnique( tx.Model(&Project{}). Where(&Project{ResourceId: p.ResourceId}). - Not(&Project{Model: gorm.Model{ID: p.ID}}), + Not(&Project{Model: Model{ID: p.ID}}), ), ), ), @@ -270,19 +293,8 @@ func (s *State) ProjectPut( return nil, saveErrorToStatus("project", err) } - // If a configuration came over the wire, either create one to attach - // to the project or update the existing one - if p.Configuration != nil { - if project.Vagrantfile != nil { - project.Vagrantfile.UpdateFromProto(p.Configuration) - } else { - project.Vagrantfile = s.VagrantfileFromProto(p.Configuration) - } - } - - result := s.db.Save(project) - if result.Error != nil { - return nil, saveErrorToStatus("project", result.Error) + if err := s.upsertFull(project); err != nil { + return nil, saveErrorToStatus("project", err) } return project.ToProto(), nil diff --git a/internal/server/singleprocess/state/runner.go b/internal/server/singleprocess/state/runner.go index 6e9737996..194770d0d 100644 --- a/internal/server/singleprocess/state/runner.go +++ b/internal/server/singleprocess/state/runner.go @@ -73,9 +73,8 @@ func (s *State) RunnerCreate(r *vagrant_server.Runner) error { return saveErrorToStatus("runner", err) } - result := s.db.Save(runner) - if result.Error != nil { - return saveErrorToStatus("runner", result.Error) + if err := s.upsertFull(runner); err != nil { + return saveErrorToStatus("runner", err) } return nil diff --git a/internal/server/singleprocess/state/target.go b/internal/server/singleprocess/state/target.go index cf01ab043..fd4c4dbfd 100644 --- a/internal/server/singleprocess/state/target.go +++ b/internal/server/singleprocess/state/target.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/go-ozzo/ozzo-validation/v4" - "github.com/hashicorp/go-hclog" "github.com/hashicorp/vagrant-plugin-sdk/proto/vagrant_plugin_sdk" "github.com/hashicorp/vagrant/internal/server" "github.com/hashicorp/vagrant/internal/server/proto/vagrant_server" @@ -17,23 +16,22 @@ func init() { } type Target struct { - gorm.Model + Model - Configuration *ProtoRaw + Configuration *ProtoValue Jobs []*InternalJob `gorm:"polymorphic:Scope;" mapstructure:"-"` Metadata MetadataSet Name *string `gorm:"uniqueIndex:idx_pname;not null"` Parent *Target `gorm:"foreignkey:ID"` - ParentID uint `mapstructure:"-"` + ParentID *uint `mapstructure:"-"` Project *Project - ProjectID *uint `gorm:"uniqueIndex:idx_pname;not null" mapstructure:"-"` + ProjectID uint `gorm:"uniqueIndex:idx_pname" mapstructure:"-"` Provider *string - Record *ProtoRaw + Record *ProtoValue ResourceId *string `gorm:"<-:create;uniqueIndex;not null"` State vagrant_server.Operation_PhysicalState Subtargets []*Target `gorm:"foreignkey:ParentID"` Uuid *string `gorm:"uniqueIndex"` - l hclog.Logger } func (t *Target) scope() interface{} { @@ -63,7 +61,7 @@ func (t *Target) validate(tx *gorm.DB) error { checkUnique( tx.Model(&Target{}). Where(&Target{Name: t.Name, ProjectID: t.ProjectID}). - Not(&Target{Model: gorm.Model{ID: t.ID}}), + Not(&Target{Model: Model{ID: t.ID}}), ), ), ), @@ -73,7 +71,7 @@ func (t *Target) validate(tx *gorm.DB) error { checkUnique( tx.Model(&Target{}). Where(&Target{ResourceId: t.ResourceId}). - Not(&Target{Model: gorm.Model{ID: t.ID}}), + Not(&Target{Model: Model{ID: t.ID}}), ), ), ), @@ -83,12 +81,17 @@ func (t *Target) validate(tx *gorm.DB) error { checkUnique( tx.Model(&Target{}). Where(&Target{Uuid: t.Uuid}). - Not(&Target{Model: gorm.Model{ID: t.ID}}), + Not(&Target{Model: Model{ID: t.ID}}), ), ), ), ), - // validation.Field(&t.ProjectID, validation.Required), TODO(spox): why are these empty? + validation.Field(&t.ProjectID, + validation.Required.When(t.Project == nil), + ), + validation.Field(&t.Project, + validation.Required.When(t.ProjectID == 0), + ), ) if err != nil { @@ -221,30 +224,33 @@ func (s *State) TargetFromProtoFuzzy( return nil, err } - if t.Project == nil { + if t.Uuid == "" && t.Name == "" { + return nil, gorm.ErrRecordNotFound + } + + if t.Project == nil && t.Uuid == "" { return nil, ErrMissingProtoParent } target = &Target{} - query := &Target{Name: &t.Name} - tx := s.db. - Preload("Project", - s.db.Where( - &Project{ResourceId: &t.Project.ResourceId}, - ), - ) - if t.Name != "" { - query.Name = &t.Name - } - if t.Uuid != "" { - query.Uuid = &t.Uuid - tx = tx.Or("uuid LIKE ?", fmt.Sprintf("%%%s%%", t.Uuid)) + if t.Project != nil { + tx := s.search(). + Joins("Project"). + Preload("Project.Basis"). + Where("Project.resource_id = ?", t.Project.ResourceId) + + result := tx.First(target, &Target{Name: &t.Name}) + if result.Error != nil { + return nil, result.Error + } + + return target, nil } - result := s.search().Joins("Project"). - Preload("Project.Basis"). - Where("Project.resource_id = ?", t.Project.ResourceId). - First(target, query) + tx := s.search().Preload("Project.Basis"). + Where("uuid LIKE ?", fmt.Sprintf("%%%s%%", t.Uuid)) + + result := tx.First(target) if result.Error != nil { return nil, result.Error } @@ -305,7 +311,7 @@ func (s *State) TargetDelete( func (s *State) TargetPut( t *vagrant_server.Target, ) (*vagrant_server.Target, error) { - target, err := s.TargetFromProto(t) + target, err := s.TargetFromProtoFuzzy(t) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return nil, lookupErrorToStatus("target", err) } @@ -315,23 +321,17 @@ func (s *State) TargetPut( target = &Target{} } - s.log.Info("pre-decode our target project", "project", target.Project) - err = s.softDecode(t, target) if err != nil { return nil, saveErrorToStatus("target", err) } - s.log.Info("post-decode our target project", "project", target.Project) - if target.Project == nil { - panic("stop") + return nil, saveErrorToStatus("target", ErrMissingProtoParent) } - result := s.db.Save(target) - s.log.Info("after save target project status", "project", target.Project, "error", err) - if result.Error != nil { - return nil, saveErrorToStatus("target", result.Error) + if err := s.upsertFull(target); err != nil { + return nil, saveErrorToStatus("target", err) } return target.ToProto(), nil diff --git a/internal/server/singleprocess/state/vagrantfile.go b/internal/server/singleprocess/state/vagrantfile.go index 6d0c2d964..7dc71520d 100644 --- a/internal/server/singleprocess/state/vagrantfile.go +++ b/internal/server/singleprocess/state/vagrantfile.go @@ -1,9 +1,7 @@ package state import ( - "github.com/hashicorp/vagrant-plugin-sdk/proto/vagrant_plugin_sdk" "github.com/hashicorp/vagrant/internal/server/proto/vagrant_server" - "gorm.io/gorm" ) type VagrantfileFormat uint8 @@ -15,13 +13,13 @@ const ( ) type Vagrantfile struct { - gorm.Model + Model Format VagrantfileFormat Unfinalized *ProtoValue Finalized *ProtoValue Raw []byte - Path string + Path *string } func init() { @@ -33,54 +31,33 @@ func (v *Vagrantfile) ToProto() *vagrant_server.Vagrantfile { return nil } - vf := &vagrant_server.Vagrantfile{ - Format: vagrant_server.Vagrantfile_Format(v.Format), - Raw: v.Raw, - } - if len(v.Path) > 0 { - vf.Path = &vagrant_plugin_sdk.Args_Path{ - Path: v.Path, - } - } - if v.Unfinalized != nil { - vf.Unfinalized = v.Unfinalized.Message.(*vagrant_plugin_sdk.Args_Hash) - } - if v.Finalized != nil { - vf.Finalized = v.Finalized.Message.(*vagrant_plugin_sdk.Args_Hash) + var file vagrant_server.Vagrantfile + if err := decode(v, &file); err != nil { + panic("failed to decode vagrantfile: " + err.Error()) } - return vf + return &file } func (v *Vagrantfile) UpdateFromProto(vf *vagrant_server.Vagrantfile) *Vagrantfile { v.Format = VagrantfileFormat(vf.Format) + v.Unfinalized = &ProtoValue{Message: vf.Unfinalized} + v.Finalized = &ProtoValue{Message: vf.Finalized} v.Raw = vf.Raw - if vf.Unfinalized != nil { - v.Unfinalized = &ProtoValue{Message: vf.Unfinalized} - } - if vf.Finalized != nil { - v.Finalized = &ProtoValue{Message: vf.Finalized} - } if vf.Path != nil { - v.Path = vf.Path.Path + v.Path = &vf.Path.Path } + return v } func (s *State) VagrantfileFromProto(v *vagrant_server.Vagrantfile) *Vagrantfile { - file := &Vagrantfile{ - Format: VagrantfileFormat(v.Format), - Raw: v.Raw, - } - if v.Unfinalized != nil { - file.Unfinalized = &ProtoValue{Message: v.Unfinalized} - } - if v.Finalized != nil { - file.Finalized = &ProtoValue{Message: v.Finalized} - } - if v.Path != nil { - file.Path = v.Path.Path + var file Vagrantfile + + err := s.decode(v, &file) + if err != nil { + panic("failed to decode vagrantfile: " + err.Error()) } - return file + return &file }