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
true - - tags - []object - - A list of available tags
- - true strategy string @@ -137,6 +130,14 @@ CDStageDeploySpec defines the desired state of CDStageDeploy. Default: Auto
false + + tags + []object + + A list of available tags. +Deprecated: Use tag instead.
+ + false