diff --git a/pkg/app/piped/trigger/determiner.go b/pkg/app/piped/trigger/determiner.go index 6185964327..45c5a7fe62 100644 --- a/pkg/app/piped/trigger/determiner.go +++ b/pkg/app/piped/trigger/determiner.go @@ -18,9 +18,11 @@ import ( "context" "fmt" "strings" + "time" "go.uber.org/zap" + "github.com/pipe-cd/pipe/pkg/app/api/service/pipedservice" "github.com/pipe-cd/pipe/pkg/config" "github.com/pipe-cd/pipe/pkg/filematcher" "github.com/pipe-cd/pipe/pkg/git" @@ -51,6 +53,10 @@ func (ds *determiners) Determiner(ck candidateKind) Determiner { type OnCommandDeterminer struct { } +func NewOnCommandDeterminer() *OnCommandDeterminer { + return &OnCommandDeterminer{} +} + // ShouldTrigger decides whether a given application should be triggered or not. func (d *OnCommandDeterminer) ShouldTrigger(_ context.Context, _ *model.Application, appCfg *config.GenericDeploymentSpec) (bool, error) { if appCfg.Trigger.OnCommand.Disabled { @@ -61,14 +67,49 @@ func (d *OnCommandDeterminer) ShouldTrigger(_ context.Context, _ *model.Applicat } type OnOutOfSyncDeterminer struct { + client apiClient +} + +func NewOnOutOfSyncDeterminer(client apiClient) *OnOutOfSyncDeterminer { + return &OnOutOfSyncDeterminer{ + client: client, + } } // ShouldTrigger decides whether a given application should be triggered or not. -func (d *OnOutOfSyncDeterminer) ShouldTrigger(_ context.Context, _ *model.Application, appCfg *config.GenericDeploymentSpec) (bool, error) { +func (d *OnOutOfSyncDeterminer) ShouldTrigger(ctx context.Context, app *model.Application, appCfg *config.GenericDeploymentSpec) (bool, error) { if *appCfg.Trigger.OnOutOfSync.Disabled { return false, nil } + // Find the most recently triggered deployment. + // Nil means it seems the application has been added recently + // and no deployment was triggered yet. + ref := app.MostRecentlyTriggeredDeployment + if ref == nil { + return true, nil + } + + resp, err := d.client.GetDeployment(ctx, &pipedservice.GetDeploymentRequest{ + Id: ref.DeploymentId, + }) + if err != nil { + return false, err + } + deployment := resp.Deployment + + // Check if it was already completed or not. + // Not yet completed means the application is deploying currently, + // so no need to trigger a new deployment for it. + if !model.IsCompletedDeployment(deployment.Status) { + return false, nil + } + + // Check the elapsed time since the last deployment. + if time.Since(time.Unix(deployment.CompletedAt, 0)) < appCfg.Trigger.OnOutOfSync.MinWindow.Duration() { + return false, nil + } + return true, nil } diff --git a/pkg/app/piped/trigger/trigger.go b/pkg/app/piped/trigger/trigger.go index 339a3e0f7f..64d7a0d836 100644 --- a/pkg/app/piped/trigger/trigger.go +++ b/pkg/app/piped/trigger/trigger.go @@ -43,6 +43,7 @@ const ( type apiClient interface { GetApplicationMostRecentDeployment(ctx context.Context, req *pipedservice.GetApplicationMostRecentDeploymentRequest, opts ...grpc.CallOption) (*pipedservice.GetApplicationMostRecentDeploymentResponse, error) CreateDeployment(ctx context.Context, in *pipedservice.CreateDeploymentRequest, opts ...grpc.CallOption) (*pipedservice.CreateDeploymentResponse, error) + GetDeployment(ctx context.Context, in *pipedservice.GetDeploymentRequest, opts ...grpc.CallOption) (*pipedservice.GetDeploymentResponse, error) ReportApplicationMostRecentDeployment(ctx context.Context, req *pipedservice.ReportApplicationMostRecentDeploymentRequest, opts ...grpc.CallOption) (*pipedservice.ReportApplicationMostRecentDeploymentResponse, error) } @@ -214,8 +215,8 @@ func (t *Trigger) checkRepoCandidates(ctx context.Context, repoID string, cs []c } ds := &determiners{ - onCommand: &OnCommandDeterminer{}, - onOutOfSync: &OnOutOfSyncDeterminer{}, + onCommand: NewOnCommandDeterminer(), + onOutOfSync: NewOnOutOfSyncDeterminer(t.apiClient), onCommit: NewOnCommitDeterminer(gitRepo, headCommit.Hash, t.commitStore, t.logger), } diff --git a/pkg/config/deployment.go b/pkg/config/deployment.go index 73aa84c515..db2fbe533f 100644 --- a/pkg/config/deployment.go +++ b/pkg/config/deployment.go @@ -90,8 +90,9 @@ type OnOutOfSync struct { // when application is at OUT_OF_SYNC state. // Default is true. Disabled *bool `json:"disabled,omitempty" default:"true"` - // TODO: Add a field to control the trigger frequency. - // MinWindow Duration `json:"minWindow,omitempty"` + // Minimum amount of time must be elapsed since the last deployment. + // This can be used to avoid triggering unnecessary continuous deployments based on OUT_OF_SYNC status. + MinWindow Duration `json:"minWindow,omitempty" default:"5m"` } func (s *GenericDeploymentSpec) Validate() error { diff --git a/pkg/config/deployment_cloudrun_test.go b/pkg/config/deployment_cloudrun_test.go index eede36488e..522752361e 100644 --- a/pkg/config/deployment_cloudrun_test.go +++ b/pkg/config/deployment_cloudrun_test.go @@ -45,7 +45,8 @@ func TestCloudRunDeploymentConfig(t *testing.T) { Disabled: false, }, OnOutOfSync: OnOutOfSync{ - Disabled: newBoolPointer(true), + Disabled: newBoolPointer(true), + MinWindow: Duration(5 * time.Minute), }, }, }, diff --git a/pkg/config/deployment_ecs_test.go b/pkg/config/deployment_ecs_test.go index f9bd7ed14e..a0155b5cf4 100644 --- a/pkg/config/deployment_ecs_test.go +++ b/pkg/config/deployment_ecs_test.go @@ -46,7 +46,8 @@ func TestECSDeploymentConfig(t *testing.T) { Disabled: false, }, OnOutOfSync: OnOutOfSync{ - Disabled: newBoolPointer(true), + Disabled: newBoolPointer(true), + MinWindow: Duration(5 * time.Minute), }, }, }, diff --git a/pkg/config/deployment_kubernetes_test.go b/pkg/config/deployment_kubernetes_test.go index b344fd5f10..ccfbd43818 100644 --- a/pkg/config/deployment_kubernetes_test.go +++ b/pkg/config/deployment_kubernetes_test.go @@ -87,7 +87,8 @@ func TestKubernetesDeploymentConfig(t *testing.T) { Disabled: false, }, OnOutOfSync: OnOutOfSync{ - Disabled: newBoolPointer(true), + Disabled: newBoolPointer(true), + MinWindow: Duration(5 * time.Minute), }, }, }, diff --git a/pkg/config/deployment_lambda_test.go b/pkg/config/deployment_lambda_test.go index 40d56d9431..21ebac1d83 100644 --- a/pkg/config/deployment_lambda_test.go +++ b/pkg/config/deployment_lambda_test.go @@ -47,7 +47,8 @@ func TestLambdaDeploymentConfig(t *testing.T) { Disabled: false, }, OnOutOfSync: OnOutOfSync{ - Disabled: newBoolPointer(true), + Disabled: newBoolPointer(true), + MinWindow: Duration(5 * time.Minute), }, }, }, @@ -93,7 +94,8 @@ func TestLambdaDeploymentConfig(t *testing.T) { }, Trigger: Trigger{ OnOutOfSync: OnOutOfSync{ - Disabled: newBoolPointer(true), + Disabled: newBoolPointer(true), + MinWindow: Duration(5 * time.Minute), }, }, }, @@ -130,7 +132,8 @@ func TestLambdaDeploymentConfig(t *testing.T) { }, Trigger: Trigger{ OnOutOfSync: OnOutOfSync{ - Disabled: newBoolPointer(true), + Disabled: newBoolPointer(true), + MinWindow: Duration(5 * time.Minute), }, }, }, diff --git a/pkg/config/deployment_terraform_test.go b/pkg/config/deployment_terraform_test.go index 8d9aa675a7..dae30249b9 100644 --- a/pkg/config/deployment_terraform_test.go +++ b/pkg/config/deployment_terraform_test.go @@ -47,7 +47,8 @@ func TestTerraformDeploymentConfig(t *testing.T) { Disabled: false, }, OnOutOfSync: OnOutOfSync{ - Disabled: newBoolPointer(true), + Disabled: newBoolPointer(true), + MinWindow: Duration(5 * time.Minute), }, }, }, @@ -70,7 +71,8 @@ func TestTerraformDeploymentConfig(t *testing.T) { Disabled: false, }, OnOutOfSync: OnOutOfSync{ - Disabled: newBoolPointer(true), + Disabled: newBoolPointer(true), + MinWindow: Duration(5 * time.Minute), }, }, }, @@ -96,7 +98,8 @@ func TestTerraformDeploymentConfig(t *testing.T) { Disabled: false, }, OnOutOfSync: OnOutOfSync{ - Disabled: newBoolPointer(false), + Disabled: newBoolPointer(false), + MinWindow: Duration(5 * time.Minute), }, }, Encryption: &SecretEncryption{ @@ -150,7 +153,8 @@ func TestTerraformDeploymentConfig(t *testing.T) { Disabled: false, }, OnOutOfSync: OnOutOfSync{ - Disabled: newBoolPointer(true), + Disabled: newBoolPointer(true), + MinWindow: Duration(5 * time.Minute), }, }, }, diff --git a/pkg/config/deployment_test.go b/pkg/config/deployment_test.go index a9a3ec10c8..08188ea225 100644 --- a/pkg/config/deployment_test.go +++ b/pkg/config/deployment_test.go @@ -277,7 +277,8 @@ func TestGenericTriggerConfiguration(t *testing.T) { }, }, OnOutOfSync: OnOutOfSync{ - Disabled: newBoolPointer(true), + Disabled: newBoolPointer(true), + MinWindow: Duration(5 * time.Minute), }, }, }, @@ -318,7 +319,8 @@ func TestTrueByDefaultBoolConfiguration(t *testing.T) { Timeout: Duration(6 * time.Hour), Trigger: Trigger{ OnOutOfSync: OnOutOfSync{ - Disabled: newBoolPointer(true), + Disabled: newBoolPointer(true), + MinWindow: Duration(5 * time.Minute), }, }, }, @@ -337,7 +339,8 @@ func TestTrueByDefaultBoolConfiguration(t *testing.T) { Timeout: Duration(6 * time.Hour), Trigger: Trigger{ OnOutOfSync: OnOutOfSync{ - Disabled: newBoolPointer(false), + Disabled: newBoolPointer(false), + MinWindow: Duration(5 * time.Minute), }, }, }, @@ -356,7 +359,8 @@ func TestTrueByDefaultBoolConfiguration(t *testing.T) { Timeout: Duration(6 * time.Hour), Trigger: Trigger{ OnOutOfSync: OnOutOfSync{ - Disabled: newBoolPointer(true), + Disabled: newBoolPointer(true), + MinWindow: Duration(5 * time.Minute), }, }, },