diff --git a/api/v1/cdstagedeploy_types.go b/api/v1/cdstagedeploy_types.go
index 5ac08382..e76a96ef 100644
--- a/api/v1/cdstagedeploy_types.go
+++ b/api/v1/cdstagedeploy_types.go
@@ -25,7 +25,9 @@ type CDStageDeploySpec struct {
// Specifies a latest available tag
Tag CodebaseTag `json:"tag"`
- // A list of available tags
+ // A list of available tags.
+ // Deprecated: Use tag instead.
+ // +optional
Tags []CodebaseTag `json:"tags"`
// TriggerType specifies a strategy for auto-deploy.
diff --git a/config/crd/bases/v2.edp.epam.com_cdstagedeployments.yaml b/config/crd/bases/v2.edp.epam.com_cdstagedeployments.yaml
index ff4d54cb..fb86bdf3 100644
--- a/config/crd/bases/v2.edp.epam.com_cdstagedeployments.yaml
+++ b/config/crd/bases/v2.edp.epam.com_cdstagedeployments.yaml
@@ -76,7 +76,9 @@ spec:
- tag
type: object
tags:
- description: A list of available tags
+ description: |-
+ A list of available tags.
+ Deprecated: Use tag instead.
items:
properties:
codebase:
@@ -92,7 +94,6 @@ spec:
- pipeline
- stage
- tag
- - tags
type: object
status:
default:
diff --git a/controllers/cdstagedeploy/chain/create_pending_trigger_template.go b/controllers/cdstagedeploy/chain/create_pending_trigger_template.go
deleted file mode 100644
index 58c6f918..00000000
--- a/controllers/cdstagedeploy/chain/create_pending_trigger_template.go
+++ /dev/null
@@ -1,118 +0,0 @@
-package chain
-
-import (
- "context"
- "errors"
- "fmt"
-
- ctrl "sigs.k8s.io/controller-runtime"
- "sigs.k8s.io/controller-runtime/pkg/client"
-
- pipelineAPi "github.com/epam/edp-cd-pipeline-operator/v2/api/v1"
-
- codebaseApi "github.com/epam/edp-codebase-operator/v2/api/v1"
- "github.com/epam/edp-codebase-operator/v2/pkg/autodeploy"
- "github.com/epam/edp-codebase-operator/v2/pkg/tektoncd"
-)
-
-type CreatePendingTriggerTemplate struct {
- k8sClient client.Client
- triggerTemplateManager tektoncd.TriggerTemplateManager
- autoDeployStrategyManager autodeploy.Manager
-}
-
-func NewCreatePendingTriggerTemplate(k8sClient client.Client, triggerTemplateManager tektoncd.TriggerTemplateManager, autoDeployStrategyManager autodeploy.Manager) *CreatePendingTriggerTemplate {
- return &CreatePendingTriggerTemplate{k8sClient: k8sClient, triggerTemplateManager: triggerTemplateManager, autoDeployStrategyManager: autoDeployStrategyManager}
-}
-
-func (h *CreatePendingTriggerTemplate) ServeRequest(
- ctx context.Context,
- stageDeploy *codebaseApi.CDStageDeploy,
-) error {
- log := ctrl.LoggerFrom(ctx).WithValues("stage", stageDeploy.Spec.Stage, "pipeline", stageDeploy.Spec.Pipeline, "status", stageDeploy.Status.Status)
-
- if skipPipelineRunCreation(stageDeploy) {
- log.Info("Skip processing TriggerTemplate for auto-deploy.")
-
- return nil
- }
-
- log.Info("Start processing TriggerTemplate for auto-deploy.")
-
- pipeline, stage, rawResource, err := getResourcesForPipelineRun(ctx, stageDeploy, h.k8sClient, h.triggerTemplateManager)
- if err != nil {
- if errors.Is(err, tektoncd.ErrEmptyTriggerTemplateResources) {
- log.Info("No resource templates found in the trigger template. Skip processing.", "triggertemplate", stage.Spec.TriggerType)
-
- stageDeploy.Status.Status = codebaseApi.CDStageDeployStatusCompleted
-
- return nil
- }
-
- return err
- }
-
- appPayload, err := h.autoDeployStrategyManager.GetAppPayloadForCurrentWithStableStrategy(
- ctx,
- stageDeploy.Spec.Tag,
- pipeline,
- stage,
- )
- if err != nil {
- if errors.Is(err, autodeploy.ErrLasTagNotFound) {
- stageDeploy.Status.Status = codebaseApi.CDStageDeployStatusCompleted
- return nil
- }
-
- return fmt.Errorf("failed to get app payload: %w", err)
- }
-
- if err = h.triggerTemplateManager.CreatePendingPipelineRun(
- ctx,
- stageDeploy.Namespace,
- stageDeploy.Name,
- rawResource,
- appPayload,
- []byte(stage.Spec.Name),
- []byte(pipeline.Spec.Name),
- []byte(stage.Spec.ClusterName),
- ); err != nil {
- return fmt.Errorf("failed to create PendingPipelineRun: %w", err)
- }
-
- stageDeploy.Status.Status = codebaseApi.CDStageDeployStatusInQueue
-
- log.Info("TriggerTemplate for auto-deploy has been processed successfully.")
-
- return nil
-}
-
-func getResourcesForPipelineRun(
- ctx context.Context,
- stageDeploy *codebaseApi.CDStageDeploy,
- k8sClient client.Client,
- triggerTemplateManager tektoncd.TriggerTemplateManager,
-) (*pipelineAPi.CDPipeline, *pipelineAPi.Stage, []byte, error) {
- pipeline := &pipelineAPi.CDPipeline{}
- if err := k8sClient.Get(ctx, client.ObjectKey{
- Namespace: stageDeploy.Namespace,
- Name: stageDeploy.Spec.Pipeline,
- }, pipeline); err != nil {
- return nil, nil, nil, fmt.Errorf("failed to get CDPipeline: %w", err)
- }
-
- stage := &pipelineAPi.Stage{}
- if err := k8sClient.Get(ctx, client.ObjectKey{
- Namespace: stageDeploy.Namespace,
- Name: stageDeploy.GetStageCRName(),
- }, stage); err != nil {
- return pipeline, nil, nil, fmt.Errorf("failed to get Stage: %w", err)
- }
-
- rawResource, err := triggerTemplateManager.GetRawResourceFromTriggerTemplate(ctx, stage.Spec.TriggerTemplate, stageDeploy.Namespace)
- if err != nil {
- return pipeline, stage, nil, fmt.Errorf("failed to get raw resource from TriggerTemplate: %w", err)
- }
-
- return pipeline, stage, rawResource, nil
-}
diff --git a/controllers/cdstagedeploy/chain/create_pending_trigger_template_test.go b/controllers/cdstagedeploy/chain/create_pending_trigger_template_test.go
deleted file mode 100644
index f3fa45f9..00000000
--- a/controllers/cdstagedeploy/chain/create_pending_trigger_template_test.go
+++ /dev/null
@@ -1,569 +0,0 @@
-package chain
-
-import (
- "context"
- "encoding/json"
- "errors"
- "fmt"
- "testing"
-
- "github.com/go-logr/logr"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
- "github.com/stretchr/testify/require"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/runtime"
- ctrl "sigs.k8s.io/controller-runtime"
- "sigs.k8s.io/controller-runtime/pkg/client"
- "sigs.k8s.io/controller-runtime/pkg/client/fake"
-
- pipelineApi "github.com/epam/edp-cd-pipeline-operator/v2/api/v1"
-
- codebaseApi "github.com/epam/edp-codebase-operator/v2/api/v1"
- "github.com/epam/edp-codebase-operator/v2/pkg/autodeploy"
- autodeploymocks "github.com/epam/edp-codebase-operator/v2/pkg/autodeploy/mocks"
- "github.com/epam/edp-codebase-operator/v2/pkg/tektoncd"
- tektoncdmocks "github.com/epam/edp-codebase-operator/v2/pkg/tektoncd/mocks"
-)
-
-func TestCreatePendingTriggerTemplate_ServeRequest(t *testing.T) {
- t.Parallel()
-
- scheme := runtime.NewScheme()
- require.NoError(t, codebaseApi.AddToScheme(scheme))
- require.NoError(t, pipelineApi.AddToScheme(scheme))
-
- type fields struct {
- k8sClient func(t *testing.T) client.Client
- triggerTemplateManager func(t *testing.T) tektoncd.TriggerTemplateManager
- autoDeployStrategyManager func(t *testing.T) autodeploy.Manager
- }
-
- tests := []struct {
- name string
- stageDeploy *codebaseApi.CDStageDeploy
- fields fields
- wantErr require.ErrorAssertionFunc
- want func(t *testing.T, d *codebaseApi.CDStageDeploy)
- }{
- {
- name: "should process TriggerTemplate for auto-deploy",
- stageDeploy: &codebaseApi.CDStageDeploy{
- ObjectMeta: metav1.ObjectMeta{
- Name: "test",
- Namespace: "default",
- },
- Spec: codebaseApi.CDStageDeploySpec{
- Pipeline: "pipe1",
- Stage: "dev",
- },
- Status: codebaseApi.CDStageDeployStatus{
- Status: codebaseApi.CDStageDeployStatusPending,
- },
- },
- fields: fields{
- k8sClient: func(t *testing.T) client.Client {
- return fake.NewClientBuilder().
- WithScheme(scheme).
- WithObjects(
- &pipelineApi.CDPipeline{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pipe1",
- Namespace: "default",
- },
- Spec: pipelineApi.CDPipelineSpec{
- Name: "pipe1",
- },
- },
- &pipelineApi.Stage{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pipe1-dev",
- Namespace: "default",
- },
- Spec: pipelineApi.StageSpec{
- TriggerTemplate: "trigger1",
- Name: "dev",
- ClusterName: "cluster-secret",
- },
- },
- ).
- Build()
- },
- triggerTemplateManager: func(t *testing.T) tektoncd.TriggerTemplateManager {
- m := tektoncdmocks.NewMockTriggerTemplateManager(t)
-
- m.On("GetRawResourceFromTriggerTemplate", mock.Anything, "trigger1", "default").
- Return([]byte("raw resource"), nil)
- m.On(
- "CreatePendingPipelineRun",
- mock.Anything,
- "default",
- "test",
- []byte("raw resource"),
- []byte("{app1: 1.0}"),
- []byte("dev"),
- []byte("pipe1"),
- []byte("cluster-secret"),
- ).Return(nil)
-
- return m
- },
- autoDeployStrategyManager: func(t *testing.T) autodeploy.Manager {
- m := autodeploymocks.NewMockManager(t)
-
- m.On("GetAppPayloadForCurrentWithStableStrategy", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
- Return(json.RawMessage("{app1: 1.0}"), nil)
-
- return m
- },
- },
- wantErr: require.NoError,
- want: func(t *testing.T, d *codebaseApi.CDStageDeploy) {
- assert.Equal(t, codebaseApi.CDStageDeployStatusInQueue, d.Status.Status)
- },
- },
- {
- name: "failed to create PipelineRun",
- stageDeploy: &codebaseApi.CDStageDeploy{
- ObjectMeta: metav1.ObjectMeta{
- Name: "test",
- Namespace: "default",
- },
- Spec: codebaseApi.CDStageDeploySpec{
- Pipeline: "pipe1",
- Stage: "dev",
- },
- Status: codebaseApi.CDStageDeployStatus{
- Status: codebaseApi.CDStageDeployStatusPending,
- },
- },
- fields: fields{
- k8sClient: func(t *testing.T) client.Client {
- return fake.NewClientBuilder().
- WithScheme(scheme).
- WithObjects(
- &pipelineApi.CDPipeline{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pipe1",
- Namespace: "default",
- },
- Spec: pipelineApi.CDPipelineSpec{
- Name: "pipe1",
- },
- },
- &pipelineApi.Stage{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pipe1-dev",
- Namespace: "default",
- },
- Spec: pipelineApi.StageSpec{
- TriggerTemplate: "trigger1",
- Name: "dev",
- ClusterName: "cluster-secret",
- },
- },
- ).
- Build()
- },
- triggerTemplateManager: func(t *testing.T) tektoncd.TriggerTemplateManager {
- m := tektoncdmocks.NewMockTriggerTemplateManager(t)
-
- m.On("GetRawResourceFromTriggerTemplate", mock.Anything, "trigger1", "default").
- Return([]byte("raw resource"), nil)
- m.On(
- "CreatePendingPipelineRun",
- mock.Anything,
- "default",
- "test",
- []byte("raw resource"),
- []byte("{app1: 1.0}"),
- []byte("dev"),
- []byte("pipe1"),
- []byte("cluster-secret"),
- ).Return(errors.New("failed to create PipelineRun"))
-
- return m
- },
- autoDeployStrategyManager: func(t *testing.T) autodeploy.Manager {
- m := autodeploymocks.NewMockManager(t)
-
- m.On("GetAppPayloadForCurrentWithStableStrategy", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
- Return(json.RawMessage("{app1: 1.0}"), nil)
-
- return m
- },
- },
- wantErr: func(t require.TestingT, err error, i ...interface{}) {
- require.Error(t, err)
- assert.Contains(t, err.Error(), "failed to create PipelineRun")
- },
- },
- {
- name: "failed to get app payload for all latest strategy",
- stageDeploy: &codebaseApi.CDStageDeploy{
- ObjectMeta: metav1.ObjectMeta{
- Name: "test",
- Namespace: "default",
- },
- Spec: codebaseApi.CDStageDeploySpec{
- Pipeline: "pipe1",
- Stage: "dev",
- },
- Status: codebaseApi.CDStageDeployStatus{
- Status: codebaseApi.CDStageDeployStatusPending,
- },
- },
- fields: fields{
- k8sClient: func(t *testing.T) client.Client {
- return fake.NewClientBuilder().
- WithScheme(scheme).
- WithObjects(
- &pipelineApi.CDPipeline{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pipe1",
- Namespace: "default",
- },
- Spec: pipelineApi.CDPipelineSpec{
- Name: "pipe1",
- },
- },
- &pipelineApi.Stage{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pipe1-dev",
- Namespace: "default",
- },
- Spec: pipelineApi.StageSpec{
- TriggerTemplate: "trigger1",
- Name: "dev",
- ClusterName: "cluster-secret",
- },
- },
- ).
- Build()
- },
- triggerTemplateManager: func(t *testing.T) tektoncd.TriggerTemplateManager {
- m := tektoncdmocks.NewMockTriggerTemplateManager(t)
-
- m.On("GetRawResourceFromTriggerTemplate", mock.Anything, "trigger1", "default").
- Return([]byte("raw resource"), nil)
-
- return m
- },
- autoDeployStrategyManager: func(t *testing.T) autodeploy.Manager {
- m := autodeploymocks.NewMockManager(t)
-
- m.On("GetAppPayloadForCurrentWithStableStrategy", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
- Return(nil, errors.New("failed to get app payload"))
-
- return m
- },
- },
- wantErr: func(t require.TestingT, err error, i ...interface{}) {
- require.Error(t, err)
- assert.Contains(t, err.Error(), "failed to get app payload")
- },
- },
- {
- name: "last tag not found",
- stageDeploy: &codebaseApi.CDStageDeploy{
- ObjectMeta: metav1.ObjectMeta{
- Name: "test",
- Namespace: "default",
- },
- Spec: codebaseApi.CDStageDeploySpec{
- Pipeline: "pipe1",
- Stage: "dev",
- },
- Status: codebaseApi.CDStageDeployStatus{
- Status: codebaseApi.CDStageDeployStatusPending,
- },
- },
- fields: fields{
- k8sClient: func(t *testing.T) client.Client {
- return fake.NewClientBuilder().
- WithScheme(scheme).
- WithObjects(
- &pipelineApi.CDPipeline{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pipe1",
- Namespace: "default",
- },
- Spec: pipelineApi.CDPipelineSpec{
- Name: "pipe1",
- },
- },
- &pipelineApi.Stage{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pipe1-dev",
- Namespace: "default",
- },
- Spec: pipelineApi.StageSpec{
- TriggerTemplate: "trigger1",
- Name: "dev",
- ClusterName: "cluster-secret",
- },
- },
- ).
- Build()
- },
- triggerTemplateManager: func(t *testing.T) tektoncd.TriggerTemplateManager {
- m := tektoncdmocks.NewMockTriggerTemplateManager(t)
-
- m.On("GetRawResourceFromTriggerTemplate", mock.Anything, "trigger1", "default").
- Return([]byte("raw resource"), nil)
-
- return m
- },
- autoDeployStrategyManager: func(t *testing.T) autodeploy.Manager {
- m := autodeploymocks.NewMockManager(t)
-
- m.On("GetAppPayloadForCurrentWithStableStrategy", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
- Return(nil, fmt.Errorf("failed to get app payload: %w", autodeploy.ErrLasTagNotFound))
-
- return m
- },
- },
- wantErr: require.NoError,
- want: func(t *testing.T, d *codebaseApi.CDStageDeploy) {
- assert.Equal(t, codebaseApi.CDStageDeployStatusCompleted, d.Status.Status)
- },
- },
- {
- name: "failed to get raw resource from trigger template",
- stageDeploy: &codebaseApi.CDStageDeploy{
- ObjectMeta: metav1.ObjectMeta{
- Name: "test",
- Namespace: "default",
- },
- Spec: codebaseApi.CDStageDeploySpec{
- Pipeline: "pipe1",
- Stage: "dev",
- },
- Status: codebaseApi.CDStageDeployStatus{
- Status: codebaseApi.CDStageDeployStatusPending,
- },
- },
- fields: fields{
- k8sClient: func(t *testing.T) client.Client {
- return fake.NewClientBuilder().
- WithScheme(scheme).
- WithObjects(
- &pipelineApi.CDPipeline{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pipe1",
- Namespace: "default",
- },
- Spec: pipelineApi.CDPipelineSpec{
- Name: "pipe1",
- },
- },
- &pipelineApi.Stage{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pipe1-dev",
- Namespace: "default",
- },
- Spec: pipelineApi.StageSpec{
- TriggerTemplate: "trigger1",
- Name: "dev",
- ClusterName: "cluster-secret",
- },
- },
- ).
- Build()
- },
- triggerTemplateManager: func(t *testing.T) tektoncd.TriggerTemplateManager {
- m := tektoncdmocks.NewMockTriggerTemplateManager(t)
-
- m.On("GetRawResourceFromTriggerTemplate", mock.Anything, "trigger1", "default").
- Return(nil, errors.New("failed to get raw resource"))
-
- return m
- },
- autoDeployStrategyManager: func(t *testing.T) autodeploy.Manager {
- return autodeploymocks.NewMockManager(t)
- },
- },
- wantErr: func(t require.TestingT, err error, i ...interface{}) {
- require.Error(t, err)
- assert.Contains(t, err.Error(), "failed to get raw resource")
- },
- },
- {
- name: "no resource templates found in the trigger template",
- stageDeploy: &codebaseApi.CDStageDeploy{
- ObjectMeta: metav1.ObjectMeta{
- Name: "test",
- Namespace: "default",
- },
- Spec: codebaseApi.CDStageDeploySpec{
- Pipeline: "pipe1",
- Stage: "dev",
- },
- Status: codebaseApi.CDStageDeployStatus{
- Status: codebaseApi.CDStageDeployStatusPending,
- },
- },
- fields: fields{
- k8sClient: func(t *testing.T) client.Client {
- return fake.NewClientBuilder().
- WithScheme(scheme).
- WithObjects(
- &pipelineApi.CDPipeline{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pipe1",
- Namespace: "default",
- },
- Spec: pipelineApi.CDPipelineSpec{
- Name: "pipe1",
- },
- },
- &pipelineApi.Stage{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pipe1-dev",
- Namespace: "default",
- },
- Spec: pipelineApi.StageSpec{
- TriggerTemplate: "trigger1",
- Name: "dev",
- ClusterName: "cluster-secret",
- },
- },
- ).
- Build()
- },
- triggerTemplateManager: func(t *testing.T) tektoncd.TriggerTemplateManager {
- m := tektoncdmocks.NewMockTriggerTemplateManager(t)
-
- m.On("GetRawResourceFromTriggerTemplate", mock.Anything, "trigger1", "default").
- Return(nil, fmt.Errorf("failed to get TriggerTemplate: %w", tektoncd.ErrEmptyTriggerTemplateResources))
-
- return m
- },
- autoDeployStrategyManager: func(t *testing.T) autodeploy.Manager {
- return autodeploymocks.NewMockManager(t)
- },
- },
- wantErr: require.NoError,
- want: func(t *testing.T, d *codebaseApi.CDStageDeploy) {
- assert.Equal(t, codebaseApi.CDStageDeployStatusCompleted, d.Status.Status)
- },
- },
- {
- name: "failed to get Stage",
- stageDeploy: &codebaseApi.CDStageDeploy{
- ObjectMeta: metav1.ObjectMeta{
- Name: "test",
- Namespace: "default",
- },
- Spec: codebaseApi.CDStageDeploySpec{
- Pipeline: "pipe1",
- Stage: "dev",
- },
- Status: codebaseApi.CDStageDeployStatus{
- Status: codebaseApi.CDStageDeployStatusPending,
- },
- },
- fields: fields{
- k8sClient: func(t *testing.T) client.Client {
- return fake.NewClientBuilder().
- WithScheme(scheme).
- WithObjects(
- &pipelineApi.CDPipeline{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pipe1",
- Namespace: "default",
- },
- Spec: pipelineApi.CDPipelineSpec{
- Name: "pipe1",
- },
- },
- ).
- Build()
- },
- triggerTemplateManager: func(t *testing.T) tektoncd.TriggerTemplateManager {
- return tektoncdmocks.NewMockTriggerTemplateManager(t)
- },
- autoDeployStrategyManager: func(t *testing.T) autodeploy.Manager {
- return autodeploymocks.NewMockManager(t)
- },
- },
- wantErr: func(t require.TestingT, err error, i ...interface{}) {
- require.Error(t, err)
- assert.Contains(t, err.Error(), "failed to get Stage")
- },
- },
- {
- name: "failed to get CDPipeline",
- stageDeploy: &codebaseApi.CDStageDeploy{
- ObjectMeta: metav1.ObjectMeta{
- Name: "test",
- Namespace: "default",
- },
- Spec: codebaseApi.CDStageDeploySpec{
- Pipeline: "pipe1",
- Stage: "dev",
- },
- Status: codebaseApi.CDStageDeployStatus{
- Status: codebaseApi.CDStageDeployStatusPending,
- },
- },
- fields: fields{
- k8sClient: func(t *testing.T) client.Client {
- return fake.NewClientBuilder().
- WithScheme(scheme).
- Build()
- },
- triggerTemplateManager: func(t *testing.T) tektoncd.TriggerTemplateManager {
- return tektoncdmocks.NewMockTriggerTemplateManager(t)
- },
- autoDeployStrategyManager: func(t *testing.T) autodeploy.Manager {
- return autodeploymocks.NewMockManager(t)
- },
- },
- wantErr: func(t require.TestingT, err error, i ...interface{}) {
- require.Error(t, err)
- assert.Contains(t, err.Error(), "failed to get CDPipeline")
- },
- },
- {
- name: "skip processing TriggerTemplate for auto-deploy",
- stageDeploy: &codebaseApi.CDStageDeploy{
- Status: codebaseApi.CDStageDeployStatus{
- Status: codebaseApi.CDStageDeployStatusInQueue,
- },
- },
- fields: fields{
- k8sClient: func(t *testing.T) client.Client {
- return fake.NewClientBuilder().
- WithScheme(scheme).
- Build()
- },
- triggerTemplateManager: func(t *testing.T) tektoncd.TriggerTemplateManager {
- return tektoncdmocks.NewMockTriggerTemplateManager(t)
- },
- autoDeployStrategyManager: func(t *testing.T) autodeploy.Manager {
- return autodeploymocks.NewMockManager(t)
- },
- },
- wantErr: require.NoError,
- want: func(t *testing.T, d *codebaseApi.CDStageDeploy) {
- assert.Equal(t, codebaseApi.CDStageDeployStatusInQueue, d.Status.Status)
- },
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- h := NewCreatePendingTriggerTemplate(
- tt.fields.k8sClient(t),
- tt.fields.triggerTemplateManager(t),
- tt.fields.autoDeployStrategyManager(t),
- )
-
- tt.wantErr(t, h.ServeRequest(ctrl.LoggerInto(context.Background(), logr.Discard()), tt.stageDeploy))
- if tt.want != nil {
- tt.want(t, tt.stageDeploy)
- }
- })
- }
-}
diff --git a/controllers/cdstagedeploy/chain/factory.go b/controllers/cdstagedeploy/chain/factory.go
index 54efa40f..1a77ab20 100644
--- a/controllers/cdstagedeploy/chain/factory.go
+++ b/controllers/cdstagedeploy/chain/factory.go
@@ -3,8 +3,6 @@ package chain
import (
"sigs.k8s.io/controller-runtime/pkg/client"
- pipelineApi "github.com/epam/edp-cd-pipeline-operator/v2/api/v1"
-
codebaseApi "github.com/epam/edp-codebase-operator/v2/api/v1"
"github.com/epam/edp-codebase-operator/v2/pkg/autodeploy"
"github.com/epam/edp-codebase-operator/v2/pkg/tektoncd"
@@ -12,19 +10,9 @@ import (
type CDStageDeployChain func(cl client.Client, stageDeploy *codebaseApi.CDStageDeploy) CDStageDeployHandler
-func CreateChain(cl client.Client, stageDeploy *codebaseApi.CDStageDeploy) CDStageDeployHandler {
+func CreateChain(cl client.Client, _ *codebaseApi.CDStageDeploy) CDStageDeployHandler {
c := chain{}
- if stageDeploy.Spec.TriggerType == pipelineApi.TriggerTypeAutoStable {
- c.Use(
- NewCreatePendingTriggerTemplate(cl, tektoncd.NewTektonTriggerTemplateManager(cl), autodeploy.NewStrategyManager(cl)),
- NewProcessPendingPipeRuns(cl),
- NewDeleteCDStageDeploy(cl),
- )
-
- return &c
- }
-
c.Use(
NewResolveStatus(cl),
NewProcessTriggerTemplate(cl, tektoncd.NewTektonTriggerTemplateManager(cl), autodeploy.NewStrategyManager(cl)),
diff --git a/controllers/cdstagedeploy/chain/process_pending_piperuns.go b/controllers/cdstagedeploy/chain/process_pending_piperuns.go
deleted file mode 100644
index 1a28dc94..00000000
--- a/controllers/cdstagedeploy/chain/process_pending_piperuns.go
+++ /dev/null
@@ -1,164 +0,0 @@
-package chain
-
-import (
- "context"
- "fmt"
-
- tektonpipelineApi "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
- "k8s.io/apimachinery/pkg/types"
- ctrl "sigs.k8s.io/controller-runtime"
- "sigs.k8s.io/controller-runtime/pkg/client"
-
- codebaseApi "github.com/epam/edp-codebase-operator/v2/api/v1"
-)
-
-type ProcessPendingPipeRuns struct {
- k8sClient client.Client
-}
-
-func NewProcessPendingPipeRuns(k8sClient client.Client) *ProcessPendingPipeRuns {
- return &ProcessPendingPipeRuns{k8sClient: k8sClient}
-}
-
-func (r *ProcessPendingPipeRuns) ServeRequest(ctx context.Context, stageDeploy *codebaseApi.CDStageDeploy) error {
- log := ctrl.LoggerFrom(ctx).WithValues("stage", stageDeploy.Spec.Stage, "pipeline", stageDeploy.Spec.Pipeline, "status", stageDeploy.Status.Status)
-
- if skipProcessingPendingPipeRuns(stageDeploy) {
- log.Info("Skip processing pending PipelineRuns for CDStageDeploy.")
-
- return nil
- }
-
- pipeRuns, err := r.getPipelines(ctx, stageDeploy)
- if err != nil {
- log.Info("Failed to get PipelineRuns.", "error", err)
-
- return nil
- }
-
- currentRun, err := getCdStageDeployPipeRun(pipeRuns, stageDeploy.Name)
- if err != nil {
- log.Info("Failed to get PipelineRun related to CDStageDeploy.", "error", err)
- return nil
- }
-
- if currentRun.IsDone() {
- stageDeploy.Status.Status = codebaseApi.CDStageDeployStatusCompleted
-
- log.Info("PipelineRun is done. CdStageDeploy is completed.")
-
- return nil
- }
-
- if currentRun.Spec.Status == tektonpipelineApi.PipelineRunSpecStatusPending {
- if shouldStartPipeRun(currentRun.Name, pipeRuns) {
- if err = r.startPipelineRun(ctx, currentRun); err != nil {
- log.Info("Failed to start PipelineRun.", "error", err)
-
- return nil
- }
-
- stageDeploy.Status.Status = codebaseApi.CDStageDeployStatusRunning
- }
-
- return nil
- }
-
- return nil
-}
-
-func skipProcessingPendingPipeRuns(stageDeploy *codebaseApi.CDStageDeploy) bool {
- if stageDeploy.IsInQueue() || stageDeploy.IsRunning() {
- return false
- }
-
- return true
-}
-
-func (r *ProcessPendingPipeRuns) startPipelineRun(ctx context.Context, pipeRun *tektonpipelineApi.PipelineRun) error {
- patch := []byte(`{"spec":{"status":""}}`)
-
- if err := r.k8sClient.Patch(ctx, pipeRun, client.RawPatch(types.MergePatchType, patch)); err != nil {
- return fmt.Errorf("failed to start PipelineRun: %w", err)
- }
-
- return nil
-}
-
-func (r *ProcessPendingPipeRuns) getPipelines(ctx context.Context, stageDeploy *codebaseApi.CDStageDeploy) (*tektonpipelineApi.PipelineRunList, error) {
- log := ctrl.LoggerFrom(ctx)
-
- pipelineRun := &tektonpipelineApi.PipelineRunList{}
-
- if err := r.k8sClient.List(
- ctx,
- pipelineRun,
- client.InNamespace(stageDeploy.Namespace),
- client.MatchingLabels{
- codebaseApi.CdPipelineLabel: stageDeploy.Spec.Pipeline,
- codebaseApi.CdStageLabel: stageDeploy.GetStageCRName(),
- },
- client.Limit(maxPipelineRuns),
- ); err != nil {
- return nil, fmt.Errorf("failed to list PipelineRuns: %w", err)
- }
-
- if len(pipelineRun.Items) > pipelineRunWarningLimit {
- log.Info("Warning: too many PipelineRuns found. Consider to clean up old PipelineRuns.")
- }
-
- return pipelineRun, nil
-}
-
-func getCdStageDeployPipeRun(runs *tektonpipelineApi.PipelineRunList, cdStageDeployName string) (*tektonpipelineApi.PipelineRun, error) {
- for i := range runs.Items {
- if runs.Items[i].Labels[codebaseApi.CdStageDeployLabel] == cdStageDeployName {
- return &runs.Items[i], nil
- }
- }
-
- return nil, fmt.Errorf("pipeline run for CDStageDeploy %v not found", cdStageDeployName)
-}
-
-func shouldStartPipeRun(pipeRunName string, runs *tektonpipelineApi.PipelineRunList) bool {
- if !allPipelineRunsCompletedExcept(runs.Items, pipeRunName) {
- return false
- }
-
- return isFirsPendingPipelineRun(runs, pipeRunName)
-}
-
-func allPipelineRunsCompletedExcept(pipelineRuns []tektonpipelineApi.PipelineRun, except string) bool {
- for i := range pipelineRuns {
- if pipelineRuns[i].Name == except || pipelineRuns[i].Spec.Status == tektonpipelineApi.PipelineRunSpecStatusPending {
- continue
- }
-
- if !pipelineRuns[i].IsDone() {
- return false
- }
- }
-
- return true
-}
-
-// isFirsPendingPipelineRun checks if the pending pipeline run is the first one in the chain based on the CreationTimestamp field.
-func isFirsPendingPipelineRun(runs *tektonpipelineApi.PipelineRunList, pipeRunName string) bool {
- if len(runs.Items) == 0 {
- return false
- }
-
- var firstRun *tektonpipelineApi.PipelineRun
-
- for i := range runs.Items {
- if runs.Items[i].Spec.Status != tektonpipelineApi.PipelineRunSpecStatusPending {
- continue
- }
-
- if firstRun == nil || runs.Items[i].CreationTimestamp.Before(&firstRun.CreationTimestamp) {
- firstRun = &runs.Items[i]
- }
- }
-
- return firstRun != nil && firstRun.Name == pipeRunName
-}
diff --git a/controllers/cdstagedeploy/chain/process_pending_piperuns_test.go b/controllers/cdstagedeploy/chain/process_pending_piperuns_test.go
deleted file mode 100644
index d28937a8..00000000
--- a/controllers/cdstagedeploy/chain/process_pending_piperuns_test.go
+++ /dev/null
@@ -1,134 +0,0 @@
-package chain
-
-import (
- "context"
- "testing"
-
- "github.com/go-logr/logr"
- "github.com/stretchr/testify/require"
- tektonpipelineApi "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/runtime"
- ctrl "sigs.k8s.io/controller-runtime"
- "sigs.k8s.io/controller-runtime/pkg/client"
- "sigs.k8s.io/controller-runtime/pkg/client/fake"
-
- codebaseApi "github.com/epam/edp-codebase-operator/v2/api/v1"
-)
-
-func TestProcessPendingPipeRuns_ServeRequest(t *testing.T) {
- t.Parallel()
-
- scheme := runtime.NewScheme()
- require.NoError(t, tektonpipelineApi.AddToScheme(scheme))
-
- tests := []struct {
- name string
- stageDeploy *codebaseApi.CDStageDeploy
- k8sClient func(t *testing.T) client.Client
- wantErr require.ErrorAssertionFunc
- want func(t *testing.T, stageDeploy *codebaseApi.CDStageDeploy)
- }{
- {
- name: "skip processing pending PipelineRuns for CDStageDeploy",
- stageDeploy: &codebaseApi.CDStageDeploy{
- Status: codebaseApi.CDStageDeployStatus{
- Status: codebaseApi.CDStageDeployStatusCompleted,
- },
- },
- k8sClient: func(t *testing.T) client.Client {
- return fake.NewClientBuilder().WithScheme(scheme).Build()
- },
- wantErr: require.NoError,
- want: func(t *testing.T, stageDeploy *codebaseApi.CDStageDeploy) {
- require.Equal(t, codebaseApi.CDStageDeployStatusCompleted, stageDeploy.Status.Status)
- },
- },
- {
- name: "pipelineRun completed, CDStageDeploy should be completed",
- stageDeploy: &codebaseApi.CDStageDeploy{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pipeline-dev-deploy",
- Namespace: "default",
- },
- Spec: codebaseApi.CDStageDeploySpec{
- Stage: "dev",
- Pipeline: "pipeline",
- },
- Status: codebaseApi.CDStageDeployStatus{
- Status: codebaseApi.CDStageDeployStatusRunning,
- },
- },
- k8sClient: func(t *testing.T) client.Client {
- run := &tektonpipelineApi.PipelineRun{
- ObjectMeta: ctrl.ObjectMeta{
- Name: "test-pipeline-run",
- Namespace: "default",
- Labels: map[string]string{
- codebaseApi.CdStageDeployLabel: "pipeline-dev-deploy",
- codebaseApi.CdPipelineLabel: "pipeline",
- codebaseApi.CdStageLabel: "pipeline-dev",
- },
- },
- }
- run.Status.MarkSucceeded("done", "done")
-
- return fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(run).WithObjects(run).Build()
- },
- wantErr: require.NoError,
- want: func(t *testing.T, stageDeploy *codebaseApi.CDStageDeploy) {
- require.Equal(t, codebaseApi.CDStageDeployStatusCompleted, stageDeploy.Status.Status)
- },
- },
- {
- name: "should start pending pipelineRun",
- stageDeploy: &codebaseApi.CDStageDeploy{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pipeline-dev-deploy",
- Namespace: "default",
- },
- Spec: codebaseApi.CDStageDeploySpec{
- Stage: "dev",
- Pipeline: "pipeline",
- },
- Status: codebaseApi.CDStageDeployStatus{
- Status: codebaseApi.CDStageDeployStatusInQueue,
- },
- },
- k8sClient: func(t *testing.T) client.Client {
- run := &tektonpipelineApi.PipelineRun{
- ObjectMeta: ctrl.ObjectMeta{
- Name: "test-pipeline-run",
- Namespace: "default",
- Labels: map[string]string{
- codebaseApi.CdStageDeployLabel: "pipeline-dev-deploy",
- codebaseApi.CdPipelineLabel: "pipeline",
- codebaseApi.CdStageLabel: "pipeline-dev",
- },
- },
- Spec: tektonpipelineApi.PipelineRunSpec{
- Status: tektonpipelineApi.PipelineRunSpecStatusPending,
- },
- }
-
- return fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(run).WithObjects(run).Build()
- },
- wantErr: require.NoError,
- want: func(t *testing.T, stageDeploy *codebaseApi.CDStageDeploy) {
- require.Equal(t, codebaseApi.CDStageDeployStatusRunning, stageDeploy.Status.Status)
- },
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- r := NewProcessPendingPipeRuns(tt.k8sClient(t))
-
- tt.wantErr(t, r.ServeRequest(ctrl.LoggerInto(context.Background(), logr.Discard()), tt.stageDeploy))
-
- if tt.want != nil {
- tt.want(t, tt.stageDeploy)
- }
- })
- }
-}
diff --git a/controllers/cdstagedeploy/chain/process_trigger_template.go b/controllers/cdstagedeploy/chain/process_trigger_template.go
index cc845ef5..f5719648 100644
--- a/controllers/cdstagedeploy/chain/process_trigger_template.go
+++ b/controllers/cdstagedeploy/chain/process_trigger_template.go
@@ -2,12 +2,15 @@ package chain
import (
"context"
+ "encoding/json"
"errors"
"fmt"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
+ pipelineApi "github.com/epam/edp-cd-pipeline-operator/v2/api/v1"
+
codebaseApi "github.com/epam/edp-codebase-operator/v2/api/v1"
"github.com/epam/edp-codebase-operator/v2/pkg/autodeploy"
"github.com/epam/edp-codebase-operator/v2/pkg/tektoncd"
@@ -55,7 +58,7 @@ func (h *ProcessTriggerTemplate) ServeRequest(ctx context.Context, stageDeploy *
return err
}
- appPayload, err := h.autoDeployStrategyManager.GetAppPayloadForAllLatestStrategy(ctx, pipeline)
+ appPayload, err := h.getAppPayload(ctx, stageDeploy, pipeline, stage)
if err != nil {
if errors.Is(err, autodeploy.ErrLasTagNotFound) {
log.Info("Codebase doesn't have tags in the CodebaseImageStream. Skip auto-deploy.")
@@ -88,6 +91,35 @@ func (h *ProcessTriggerTemplate) ServeRequest(ctx context.Context, stageDeploy *
return nil
}
+func (h *ProcessTriggerTemplate) getAppPayload(
+ ctx context.Context,
+ stageDeploy *codebaseApi.CDStageDeploy,
+ pipeline *pipelineApi.CDPipeline,
+ stage *pipelineApi.Stage,
+) (json.RawMessage, error) {
+ var (
+ payload json.RawMessage
+ err error
+ )
+
+ if stageDeploy.Spec.TriggerType == pipelineApi.TriggerTypeAutoStable {
+ payload, err = h.autoDeployStrategyManager.GetAppPayloadForCurrentWithStableStrategy(
+ ctx,
+ stageDeploy.Spec.Tag,
+ pipeline,
+ stage,
+ )
+ } else {
+ payload, err = h.autoDeployStrategyManager.GetAppPayloadForAllLatestStrategy(ctx, pipeline)
+ }
+
+ if err != nil {
+ return nil, fmt.Errorf("failed to get application payload: %w", err)
+ }
+
+ return payload, nil
+}
+
func skipPipelineRunCreation(stageDeploy *codebaseApi.CDStageDeploy) bool {
if stageDeploy.IsPending() || stageDeploy.IsFailed() {
return false
@@ -95,3 +127,33 @@ func skipPipelineRunCreation(stageDeploy *codebaseApi.CDStageDeploy) bool {
return true
}
+
+func getResourcesForPipelineRun(
+ ctx context.Context,
+ stageDeploy *codebaseApi.CDStageDeploy,
+ k8sClient client.Client,
+ triggerTemplateManager tektoncd.TriggerTemplateManager,
+) (*pipelineApi.CDPipeline, *pipelineApi.Stage, []byte, error) {
+ pipeline := &pipelineApi.CDPipeline{}
+ if err := k8sClient.Get(ctx, client.ObjectKey{
+ Namespace: stageDeploy.Namespace,
+ Name: stageDeploy.Spec.Pipeline,
+ }, pipeline); err != nil {
+ return nil, nil, nil, fmt.Errorf("failed to get CDPipeline: %w", err)
+ }
+
+ stage := &pipelineApi.Stage{}
+ if err := k8sClient.Get(ctx, client.ObjectKey{
+ Namespace: stageDeploy.Namespace,
+ Name: stageDeploy.GetStageCRName(),
+ }, stage); err != nil {
+ return pipeline, nil, nil, fmt.Errorf("failed to get Stage: %w", err)
+ }
+
+ rawResource, err := triggerTemplateManager.GetRawResourceFromTriggerTemplate(ctx, stage.Spec.TriggerTemplate, stageDeploy.Namespace)
+ if err != nil {
+ return pipeline, stage, nil, fmt.Errorf("failed to get raw resource from TriggerTemplate: %w", err)
+ }
+
+ return pipeline, stage, rawResource, nil
+}
diff --git a/controllers/cdstagedeploy/chain/process_trigger_template_test.go b/controllers/cdstagedeploy/chain/process_trigger_template_test.go
index ef2f370c..6318b8d9 100644
--- a/controllers/cdstagedeploy/chain/process_trigger_template_test.go
+++ b/controllers/cdstagedeploy/chain/process_trigger_template_test.go
@@ -122,6 +122,96 @@ func TestProcessTriggerTemplate_ServeRequest(t *testing.T) {
assert.Equal(t, codebaseApi.CDStageDeployStatusRunning, d.Status.Status)
},
},
+ {
+ name: "should process TriggerTemplate for Auto-stable auto-deploy",
+ stageDeploy: &codebaseApi.CDStageDeploy{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test",
+ Namespace: "default",
+ },
+ Spec: codebaseApi.CDStageDeploySpec{
+ Pipeline: "pipe1",
+ Stage: "dev",
+ TriggerType: pipelineApi.TriggerTypeAutoStable,
+ Tag: codebaseApi.CodebaseTag{
+ Codebase: "app1",
+ Tag: "1.0",
+ },
+ },
+ Status: codebaseApi.CDStageDeployStatus{
+ Status: codebaseApi.CDStageDeployStatusPending,
+ },
+ },
+ fields: fields{
+ k8sClient: func(t *testing.T) client.Client {
+ return fake.NewClientBuilder().
+ WithScheme(scheme).
+ WithObjects(
+ &pipelineApi.CDPipeline{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "pipe1",
+ Namespace: "default",
+ },
+ Spec: pipelineApi.CDPipelineSpec{
+ Name: "pipe1",
+ },
+ },
+ &pipelineApi.Stage{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "pipe1-dev",
+ Namespace: "default",
+ },
+ Spec: pipelineApi.StageSpec{
+ TriggerTemplate: "trigger1",
+ Name: "dev",
+ ClusterName: "cluster-secret",
+ },
+ },
+ ).
+ Build()
+ },
+ triggerTemplateManager: func(t *testing.T) tektoncd.TriggerTemplateManager {
+ m := tektoncdmocks.NewMockTriggerTemplateManager(t)
+
+ m.On("GetRawResourceFromTriggerTemplate", mock.Anything, "trigger1", "default").
+ Return([]byte("raw resource"), nil)
+ m.On(
+ "CreatePipelineRun",
+ mock.Anything,
+ "default",
+ "test",
+ []byte("raw resource"),
+ []byte("{app1: 1.0}"),
+ []byte("dev"),
+ []byte("pipe1"),
+ []byte("cluster-secret"),
+ ).Return(nil)
+
+ return m
+ },
+ autoDeployStrategyManager: func(t *testing.T) autodeploy.Manager {
+ m := autodeploymocks.NewMockManager(t)
+
+ m.On(
+ "GetAppPayloadForCurrentWithStableStrategy",
+ mock.Anything,
+ codebaseApi.CodebaseTag{
+ Codebase: "app1",
+ Tag: "1.0",
+ },
+ mock.Anything,
+ mock.Anything,
+ ).
+ Return(json.RawMessage("{app1: 1.0}"), nil)
+
+ return m
+ },
+ },
+ wantErr: require.NoError,
+ want: func(t *testing.T, d *codebaseApi.CDStageDeploy) {
+ assert.Equal(t, codebaseApi.CDStageDeployStatusRunning, d.Status.Status)
+ },
+ },
{
name: "failed to create PipelineRun",
stageDeploy: &codebaseApi.CDStageDeploy{
diff --git a/controllers/cdstagedeploy/chain/resolve_status.go b/controllers/cdstagedeploy/chain/resolve_status.go
index 8dc5c0ab..c6421df3 100644
--- a/controllers/cdstagedeploy/chain/resolve_status.go
+++ b/controllers/cdstagedeploy/chain/resolve_status.go
@@ -52,33 +52,24 @@ func (r *ResolveStatus) ServeRequest(ctx context.Context, stageDeploy *codebaseA
}
if stageDeploy.IsInQueue() {
- if allPipelineRunsCompleted(pipelineRun.Items) {
- log.Info("All PipelineRuns have been completed.")
-
- hasRunning, err := r.hasRunningCDStageDeploys(
- ctx,
- stageDeploy.Name,
- stageDeploy.GetStageCRName(),
- stageDeploy.Spec.Pipeline,
- stageDeploy.Namespace,
- )
- if err != nil {
- return fmt.Errorf("failed to check running CDStageDeploys: %w", err)
- }
-
- if hasRunning {
- log.Info("Some CDStageDeploys are still running.")
+ shouldStart, err := r.shouldStartCDStageDeploy(
+ ctx,
+ stageDeploy.Name,
+ stageDeploy.GetStageCRName(),
+ stageDeploy.Spec.Pipeline,
+ stageDeploy.Namespace,
+ pipelineRun.Items,
+ )
+ if err != nil {
+ return fmt.Errorf("failed to check running CDStageDeploys: %w", err)
+ }
- return nil
- }
+ if shouldStart {
+ log.Info("Starting processing CDStageDeploy.")
stageDeploy.Status.Status = codebaseApi.CDStageDeployStatusPending
-
- return nil
}
- log.Info("Some PipelineRuns are still running.")
-
return nil
}
@@ -124,10 +115,19 @@ func (r *ResolveStatus) getRunningPipelines(ctx context.Context, stageDeploy *co
return pipelineRun, nil
}
-func (r *ResolveStatus) hasRunningCDStageDeploys(
+func (r *ResolveStatus) shouldStartCDStageDeploy(
ctx context.Context,
currentCDStageDeployName, stage, pipeline, namespace string,
+ pipelines []tektonpipelineApi.PipelineRun,
) (bool, error) {
+ log := ctrl.LoggerFrom(ctx)
+
+ if !allPipelineRunsCompleted(pipelines) {
+ log.Info("Some PipelineRuns are still running.")
+
+ return false, nil
+ }
+
cdStageDeploy := &codebaseApi.CDStageDeployList{}
if err := r.client.List(
@@ -142,14 +142,29 @@ func (r *ResolveStatus) hasRunningCDStageDeploys(
return false, fmt.Errorf("failed to list CDStageDeploys: %w", err)
}
+ if !allCdStageDeploysInQue(cdStageDeploy) {
+ log.Info("Some CDStageDeploys are processing.")
+
+ return false, nil
+ }
+
+ if !isFirsCdStageDeployInQue(cdStageDeploy, currentCDStageDeployName) {
+ log.Info("Another CDStageDeploys is in queue before current.")
+
+ return false, nil
+ }
+
+ return true, nil
+}
+
+func allCdStageDeploysInQue(cdStageDeploy *codebaseApi.CDStageDeployList) bool {
for i := range cdStageDeploy.Items {
- if cdStageDeploy.Items[i].Name != currentCDStageDeployName &&
- cdStageDeploy.Items[i].Status.Status == codebaseApi.CDStageDeployStatusRunning {
- return true, nil
+ if cdStageDeploy.Items[i].Status.Status != codebaseApi.CDStageDeployStatusInQueue {
+ return false
}
}
- return false, nil
+ return true
}
func allPipelineRunsCompleted(pipelineRuns []tektonpipelineApi.PipelineRun) bool {
@@ -161,3 +176,19 @@ func allPipelineRunsCompleted(pipelineRuns []tektonpipelineApi.PipelineRun) bool
return true
}
+
+func isFirsCdStageDeployInQue(deploys *codebaseApi.CDStageDeployList, currentCDStageDeployName string) bool {
+ if len(deploys.Items) == 0 {
+ return false
+ }
+
+ var firstDeploy *codebaseApi.CDStageDeploy
+
+ for i := range deploys.Items {
+ if firstDeploy == nil || deploys.Items[i].CreationTimestamp.Before(&firstDeploy.CreationTimestamp) {
+ firstDeploy = &deploys.Items[i]
+ }
+ }
+
+ return firstDeploy != nil && firstDeploy.Name == currentCDStageDeployName
+}
diff --git a/controllers/cdstagedeploy/chain/resolve_status_test.go b/controllers/cdstagedeploy/chain/resolve_status_test.go
index 7338d5a4..0080b4c3 100644
--- a/controllers/cdstagedeploy/chain/resolve_status_test.go
+++ b/controllers/cdstagedeploy/chain/resolve_status_test.go
@@ -3,6 +3,7 @@ package chain
import (
"context"
"testing"
+ "time"
"github.com/go-logr/logr"
"github.com/stretchr/testify/require"
@@ -132,6 +133,10 @@ func TestResolveStatus_ServeRequest(t *testing.T) {
{
name: "queued CDStageDeploy should be pending after all pipeline runs completed",
stageDeploy: &codebaseApi.CDStageDeploy{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test",
+ Namespace: "default",
+ },
Spec: codebaseApi.CDStageDeploySpec{
Pipeline: "app1",
Stage: "dev",
@@ -141,14 +146,95 @@ func TestResolveStatus_ServeRequest(t *testing.T) {
},
},
k8sClient: func(t *testing.T) client.Client {
- return fake.NewClientBuilder().WithScheme(scheme).Build()
+ d := &codebaseApi.CDStageDeploy{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test",
+ Namespace: "default",
+ Labels: map[string]string{
+ codebaseApi.CdPipelineLabel: "app1",
+ codebaseApi.CdStageLabel: "app1-dev",
+ },
+ },
+ Spec: codebaseApi.CDStageDeploySpec{
+ Pipeline: "app1",
+ Stage: "dev",
+ },
+ Status: codebaseApi.CDStageDeployStatus{
+ Status: codebaseApi.CDStageDeployStatusInQueue,
+ },
+ }
+
+ return fake.NewClientBuilder().WithScheme(scheme).WithObjects(d).WithStatusSubresource(d).Build()
},
wantErr: require.NoError,
wantStatus: codebaseApi.CDStageDeployStatusPending,
},
+ {
+ name: "queued CDStageDeploy should be queued if it is not first in queue",
+ stageDeploy: &codebaseApi.CDStageDeploy{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test",
+ Namespace: "default",
+ },
+ Spec: codebaseApi.CDStageDeploySpec{
+ Pipeline: "app1",
+ Stage: "dev",
+ },
+ Status: codebaseApi.CDStageDeployStatus{
+ Status: codebaseApi.CDStageDeployStatusInQueue,
+ },
+ },
+ k8sClient: func(t *testing.T) client.Client {
+ d1 := &codebaseApi.CDStageDeploy{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test",
+ Namespace: "default",
+ Labels: map[string]string{
+ codebaseApi.CdPipelineLabel: "app1",
+ codebaseApi.CdStageLabel: "app1-dev",
+ },
+ CreationTimestamp: metav1.NewTime(time.Now()),
+ },
+ Spec: codebaseApi.CDStageDeploySpec{
+ Pipeline: "app1",
+ Stage: "dev",
+ },
+ Status: codebaseApi.CDStageDeployStatus{
+ Status: codebaseApi.CDStageDeployStatusInQueue,
+ },
+ }
+
+ d2 := &codebaseApi.CDStageDeploy{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test2",
+ Namespace: "default",
+ Labels: map[string]string{
+ codebaseApi.CdPipelineLabel: "app1",
+ codebaseApi.CdStageLabel: "app1-dev",
+ },
+ CreationTimestamp: metav1.NewTime(time.Now().Add(-time.Hour)),
+ },
+ Spec: codebaseApi.CDStageDeploySpec{
+ Pipeline: "app1",
+ Stage: "dev",
+ },
+ Status: codebaseApi.CDStageDeployStatus{
+ Status: codebaseApi.CDStageDeployStatusInQueue,
+ },
+ }
+
+ return fake.NewClientBuilder().WithScheme(scheme).WithObjects(d1, d2).WithStatusSubresource(d1, d2).Build()
+ },
+ wantErr: require.NoError,
+ wantStatus: codebaseApi.CDStageDeployStatusInQueue,
+ },
{
name: "queued CDStageDeploy should be queued if not all pipeline runs completed",
stageDeploy: &codebaseApi.CDStageDeploy{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test",
+ Namespace: "default",
+ },
Spec: codebaseApi.CDStageDeploySpec{
Pipeline: "app1",
Stage: "dev",
diff --git a/controllers/codebaseimagestream/chain/put_cd_stage_deploy.go b/controllers/codebaseimagestream/chain/put_cd_stage_deploy.go
index c9110cf8..980f74f8 100644
--- a/controllers/codebaseimagestream/chain/put_cd_stage_deploy.go
+++ b/controllers/codebaseimagestream/chain/put_cd_stage_deploy.go
@@ -30,7 +30,6 @@ type cdStageDeployCommand struct {
Stage string
TriggerType string
Tag codebaseApi.CodebaseTag
- Tags []codebaseApi.CodebaseTag
}
func (h PutCDStageDeploy) ServeRequest(ctx context.Context, imageStream *codebaseApi.CodebaseImageStream) error {
@@ -188,12 +187,6 @@ func getCreateCommand(
Codebase: codebase,
Tag: lastTag.Name,
},
- Tags: []codebaseApi.CodebaseTag{
- {
- Codebase: codebase,
- Tag: lastTag.Name,
- },
- },
}, nil
}
@@ -210,7 +203,7 @@ func (h PutCDStageDeploy) create(ctx context.Context, command *cdStageDeployComm
Pipeline: command.Pipeline,
Stage: command.Stage,
Tag: command.Tag,
- Tags: command.Tags,
+ Tags: []codebaseApi.CodebaseTag{command.Tag},
TriggerType: command.TriggerType,
},
}
diff --git a/deploy-templates/crds/v2.edp.epam.com_cdstagedeployments.yaml b/deploy-templates/crds/v2.edp.epam.com_cdstagedeployments.yaml
index ff4d54cb..fb86bdf3 100644
--- a/deploy-templates/crds/v2.edp.epam.com_cdstagedeployments.yaml
+++ b/deploy-templates/crds/v2.edp.epam.com_cdstagedeployments.yaml
@@ -76,7 +76,9 @@ spec:
- tag
type: object
tags:
- description: A list of available tags
+ description: |-
+ A list of available tags.
+ Deprecated: Use tag instead.
items:
properties:
codebase:
@@ -92,7 +94,6 @@ spec:
- pipeline
- stage
- tag
- - tags
type: object
status:
default:
diff --git a/docs/api.md b/docs/api.md
index e09d7c44..2c4219cf 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -121,13 +121,6 @@ CDStageDeploySpec defines the desired state of CDStageDeploy.
Specifies a latest available tag