2023-06-05 14:18:05 -07:00

355 lines
7.4 KiB
Go

package state
import (
"errors"
"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"
"gorm.io/gorm"
)
func init() {
models = append(models, &Target{})
}
type Target struct {
gorm.Model
Configuration *ProtoRaw
Jobs []*InternalJob `gorm:"polymorphic:Scope;" mapstructure:"-"`
Metadata MetadataSet
Name *string `gorm:"uniqueIndex:idx_pname;not null"`
Parent *Target `gorm:"foreignkey:ID"`
ParentID uint `mapstructure:"-"`
Project *Project
ProjectID *uint `gorm:"uniqueIndex:idx_pname;not null" mapstructure:"-"`
Provider *string
Record *ProtoRaw
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{} {
return t
}
// Set a public ID on the target before creating
func (t *Target) BeforeSave(tx *gorm.DB) error {
if t.ResourceId == nil {
if err := t.setId(); err != nil {
return err
}
}
if err := t.validate(tx); err != nil {
return err
}
return nil
}
func (t *Target) validate(tx *gorm.DB) error {
err := validation.ValidateStruct(t,
validation.Field(&t.Name,
validation.Required,
validation.By(
checkUnique(
tx.Model(&Target{}).
Where(&Target{Name: t.Name, ProjectID: t.ProjectID}).
Not(&Target{Model: gorm.Model{ID: t.ID}}),
),
),
),
validation.Field(&t.ResourceId,
validation.Required,
validation.By(
checkUnique(
tx.Model(&Target{}).
Where(&Target{ResourceId: t.ResourceId}).
Not(&Target{Model: gorm.Model{ID: t.ID}}),
),
),
),
validation.Field(&t.Uuid,
validation.When(t.Uuid != nil,
validation.By(
checkUnique(
tx.Model(&Target{}).
Where(&Target{Uuid: t.Uuid}).
Not(&Target{Model: gorm.Model{ID: t.ID}}),
),
),
),
),
// validation.Field(&t.ProjectID, validation.Required), TODO(spox): why are these empty?
)
if err != nil {
return err
}
return nil
}
func (t *Target) setId() error {
id, err := server.Id()
if err != nil {
return err
}
t.ResourceId = &id
return nil
}
// Convert target to reference protobuf message
func (t *Target) ToProtoRef() *vagrant_plugin_sdk.Ref_Target {
if t == nil {
return nil
}
var ref vagrant_plugin_sdk.Ref_Target
err := decode(t, &ref)
if err != nil {
panic("failed to decode target to ref: " + err.Error())
}
return &ref
}
// Convert target to protobuf message
func (t *Target) ToProto() *vagrant_server.Target {
if t == nil {
return nil
}
var target vagrant_server.Target
err := decode(t, &target)
if err != nil {
panic("failed to decode target: " + err.Error())
}
return &target
}
// Load a Target from reference protobuf message
func (s *State) TargetFromProtoRef(
ref *vagrant_plugin_sdk.Ref_Target,
) (*Target, error) {
if ref == nil {
return nil, ErrEmptyProtoArgument
}
if ref.ResourceId == "" {
return nil, gorm.ErrRecordNotFound
}
var target Target
result := s.search().Preload("Project.Basis").First(&target,
&Target{ResourceId: &ref.ResourceId},
)
if result.Error != nil {
return nil, result.Error
}
return &target, nil
}
func (s *State) TargetFromProtoRefFuzzy(
ref *vagrant_plugin_sdk.Ref_Target,
) (*Target, error) {
target, err := s.TargetFromProtoRef(ref)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, err
}
if ref.Project == nil {
return nil, ErrMissingProtoParent
}
if ref.Name == "" {
return nil, gorm.ErrRecordNotFound
}
target = &Target{}
result := s.search().
Joins("Project", &Project{ResourceId: &ref.Project.ResourceId}).
Preload("Project.Basis").
First(target, &Target{Name: &ref.Name})
if result.Error != nil {
return nil, result.Error
}
return target, nil
}
// Load a Target from protobuf message
func (s *State) TargetFromProto(
t *vagrant_server.Target,
) (*Target, error) {
target, err := s.TargetFromProtoRef(
&vagrant_plugin_sdk.Ref_Target{
ResourceId: t.ResourceId,
},
)
if err != nil {
return nil, err
}
return target, nil
}
func (s *State) TargetFromProtoFuzzy(
t *vagrant_server.Target,
) (*Target, error) {
target, err := s.TargetFromProto(t)
if err == nil {
return target, nil
}
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, err
}
if t.Project == nil {
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))
}
result := s.search().Joins("Project").
Preload("Project.Basis").
Where("Project.resource_id = ?", t.Project.ResourceId).
First(target, query)
if result.Error != nil {
return nil, result.Error
}
return target, nil
}
// Get a target record using a reference protobuf message
func (s *State) TargetGet(
ref *vagrant_plugin_sdk.Ref_Target,
) (*vagrant_server.Target, error) {
t, err := s.TargetFromProtoRef(ref)
if err != nil {
return nil, lookupErrorToStatus("target", err)
}
return t.ToProto(), nil
}
// List all target records
func (s *State) TargetList() ([]*vagrant_plugin_sdk.Ref_Target, error) {
var targets []Target
result := s.search().Find(&targets)
if result.Error != nil {
return nil, lookupErrorToStatus("targets", result.Error)
}
trefs := make([]*vagrant_plugin_sdk.Ref_Target, len(targets))
for i, t := range targets {
trefs[i] = t.ToProtoRef()
}
return trefs, nil
}
// Delete a target by reference protobuf message
func (s *State) TargetDelete(
t *vagrant_plugin_sdk.Ref_Target,
) error {
target, err := s.TargetFromProtoRef(t)
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
return nil
}
if err != nil {
return lookupErrorToStatus("target", err)
}
result := s.db.Delete(target)
if result.Error != nil {
return deleteErrorToStatus("target", result.Error)
}
return nil
}
// Store a Target
func (s *State) TargetPut(
t *vagrant_server.Target,
) (*vagrant_server.Target, error) {
target, err := s.TargetFromProto(t)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, lookupErrorToStatus("target", err)
}
// Make sure we don't have a nil
if err != nil {
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")
}
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)
}
return target.ToProto(), nil
}
// Find a Target
func (s *State) TargetFind(
t *vagrant_server.Target,
) (*vagrant_server.Target, error) {
target, err := s.TargetFromProtoFuzzy(t)
if err != nil {
return nil, lookupErrorToStatus("target", err)
}
return target.ToProto(), nil
}
var (
_ scope = (*Target)(nil)
)