Skip to content
This repository was archived by the owner on May 30, 2024. It is now read-only.

Support the pipeline serialization #386

Merged
merged 5 commits into from
Mar 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions internal/interactor/_mock.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ github.com/gitploy-io/gitploy/internal/interactor=user.go\
,github.com/gitploy-io/gitploy/internal/interactor=deploymentstatistics.go\
,github.com/gitploy-io/gitploy/internal/interactor=lock.go\
,github.com/gitploy-io/gitploy/internal/interactor=event.go\
,github.com/gitploy-io/gitploy/internal/interactor=review.go\
-source ./interface.go \
-package mock \
-destination ./mock/pkg.go
26 changes: 15 additions & 11 deletions internal/interactor/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type (
FindDeploymentByID(ctx context.Context, id int) (*ent.Deployment, error)
FindDeploymentByUID(ctx context.Context, uid int64) (*ent.Deployment, error)
FindDeploymentOfRepoByNumber(ctx context.Context, r *ent.Repo, number int) (*ent.Deployment, error)
FindPrevRunningDeployment(ctx context.Context, d *ent.Deployment) (*ent.Deployment, error)
FindPrevSuccessDeployment(ctx context.Context, d *ent.Deployment) (*ent.Deployment, error)
GetNextDeploymentNumberOfRepo(ctx context.Context, r *ent.Repo) (int, error)
CreateDeployment(ctx context.Context, d *ent.Deployment) (*ent.Deployment, error)
Expand Down Expand Up @@ -71,21 +72,12 @@ type (
// But if it requires a review, it saves the payload on the store and waits until reviewed.
// It returns an error for a undeployable payload.
func (i *DeploymentInteractor) Deploy(ctx context.Context, u *ent.User, r *ent.Repo, d *ent.Deployment, env *extent.Env) (*ent.Deployment, error) {
i.log.Debug("Validate the request.")
v := NewDeploymentValidator([]Validator{
&RefValidator{Env: env},
&FrozenWindowValidator{Env: env},
&LockValidator{Repo: r, Store: i.store},
})
if err := v.Validate(d); err != nil {
return nil, err
}

number, err := i.store.GetNextDeploymentNumberOfRepo(ctx, r)
if err != nil {
return nil, e.NewError(e.ErrorCodeInternalError, err)
}

i.log.Debug("Get the next number, and build the deployment.")
d = &ent.Deployment{
Number: number,
Type: d.Type,
Expand All @@ -98,6 +90,17 @@ func (i *DeploymentInteractor) Deploy(ctx context.Context, u *ent.User, r *ent.R
RepoID: r.ID,
}

i.log.Debug("Validate the deployment before a request.")
v := NewDeploymentValidator([]Validator{
&RefValidator{Env: env},
&FrozenWindowValidator{Env: env},
&LockValidator{Repo: r, Store: i.store},
&SerializationValidator{Env: env, Store: i.store},
})
if err := v.Validate(d); err != nil {
return nil, err
}

if env.HasReview() {
i.log.Debug("Save the deployment to wait reviews.")
d, err = i.store.CreateDeployment(ctx, d)
Expand Down Expand Up @@ -199,12 +202,13 @@ func (i *DeploymentInteractor) requestReviews(ctx context.Context, u *ent.User,
// after review has finished.
// It returns an error for a undeployable payload.
func (i *DeploymentInteractor) DeployToRemote(ctx context.Context, u *ent.User, r *ent.Repo, d *ent.Deployment, env *extent.Env) (*ent.Deployment, error) {
i.log.Debug("Validate the request.")
i.log.Debug("Validate the deployment before a request.")
v := NewDeploymentValidator([]Validator{
&StatusValidator{Status: deployment.StatusWaiting},
&RefValidator{Env: env},
&FrozenWindowValidator{Env: env},
&LockValidator{Repo: r, Store: i.store},
&SerializationValidator{Env: env, Store: i.store},
&ReviewValidator{Store: i.store},
})
if err := v.Validate(d); err != nil {
Expand Down
128 changes: 15 additions & 113 deletions internal/interactor/mock/pkg.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 31 additions & 0 deletions internal/interactor/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/gitploy-io/gitploy/model/ent/review"
"github.com/gitploy-io/gitploy/model/extent"
"github.com/gitploy-io/gitploy/pkg/e"
"go.uber.org/zap"
)

// DeploymentValidator validate that it is deployable.
Expand Down Expand Up @@ -137,3 +138,33 @@ func (v *ReviewValidator) Validate(d *ent.Deployment) error {

return e.NewError(e.ErrorCodeDeploymentNotApproved, nil)
}

// SerializationValidator verify if there is currently a running deployment
// for the environment.
type SerializationValidator struct {
Env *extent.Env
Store DeploymentStore
}

func (v *SerializationValidator) Validate(d *ent.Deployment) error {
log := zap.L().Named("serialization-validator")
defer log.Sync()

// Skip if the serialization field is disabled.
if v.Env.Serialization == nil || !*v.Env.Serialization {
log.Debug("Skip the serialization validator.")
return nil
}

d, err := v.Store.FindPrevRunningDeployment(context.Background(), d)
if d != nil {
return e.NewError(e.ErrorCodeDeploymentSerialization, nil)
}

if e.HasErrorCode(err, e.ErrorCodeEntityNotFound) {
log.Debug("There is no running deployment.")
return nil
}

return err
}
36 changes: 36 additions & 0 deletions internal/interactor/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,39 @@ func TestReviewValidator_Validate(t *testing.T) {
}
})
}

func TestSerializationValidator_Validate(t *testing.T) {
t.Run("Returns nil if the serialization is empty.", func(t *testing.T) {
ctrl := gomock.NewController(t)
store := mock.NewMockStore(ctrl)

v := &i.SerializationValidator{
Env: &extent.Env{},
Store: store,
}

if err := v.Validate(&ent.Deployment{}); err != nil {
t.Fatalf("Validate returns an error: %v", err)
}
})

t.Run("Returns an deployment_serialization error if there is a running deployment.", func(t *testing.T) {
t.Log("Start mocking:")
ctrl := gomock.NewController(t)
store := mock.NewMockStore(ctrl)

t.Log("Return a running deployment.")
store.EXPECT().
FindPrevRunningDeployment(gomock.Any(), gomock.AssignableToTypeOf(&ent.Deployment{})).
Return(&ent.Deployment{}, nil)

v := &i.SerializationValidator{
Env: &extent.Env{Serialization: pointer.ToBool(true)},
Store: store,
}

if err := v.Validate(&ent.Deployment{}); !e.HasErrorCode(err, e.ErrorCodeDeploymentSerialization) {
t.Fatalf("Error is not deployment_serialization: %v", err)
}
})
}
26 changes: 26 additions & 0 deletions internal/pkg/store/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,32 @@ func (s *Store) GetNextDeploymentNumberOfRepo(ctx context.Context, r *ent.Repo)
return cnt + 1, nil
}

// FindPrevRunningDeployment find a deployment of which the status is created, queued, or running.
func (s *Store) FindPrevRunningDeployment(ctx context.Context, d *ent.Deployment) (*ent.Deployment, error) {
d, err := s.c.Deployment.
Query().
Where(
deployment.And(
deployment.RepoIDEQ(d.RepoID),
deployment.EnvEQ(d.Env),
deployment.StatusIn(
deployment.StatusCreated,
deployment.StatusQueued,
deployment.StatusRunning,
),
),
).
Order(ent.Desc(deployment.FieldCreatedAt)).
First(ctx)
if ent.IsNotFound(err) {
return nil, e.NewErrorWithMessage(e.ErrorCodeEntityNotFound, "The deployment is not found.", err)
} else if err != nil {
return nil, e.NewError(e.ErrorCodeInternalError, err)
}

return d, nil
}

func (s *Store) FindPrevSuccessDeployment(ctx context.Context, d *ent.Deployment) (*ent.Deployment, error) {
d, err := s.c.Deployment.
Query().
Expand Down
Loading