355 lines
7.4 KiB
Go
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)
|
|
)
|