Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .mockery.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,9 @@ packages:
github.com/epam/edp-codebase-operator/v2/pkg/gitprovider:
interfaces:
GitProjectProvider:
github.com/epam/edp-codebase-operator/v2/pkg/tektoncd:
interfaces:
TriggerTemplateManager:
github.com/epam/edp-codebase-operator/v2/pkg/autodeploy:
interfaces:
Manager:
25 changes: 25 additions & 0 deletions api/v1/cdstagedeploy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ type CDStageDeploySpec struct {

// A list of available tags
Tags []CodebaseTag `json:"tags"`

// TriggerType specifies a strategy for auto-deploy.
// +optional
// +kubebuilder:default="Auto"
TriggerType string `json:"strategy,omitempty"`
}

type CodebaseTag struct {
Expand Down Expand Up @@ -75,6 +80,26 @@ func (in *CDStageDeploy) GetStageCRName() string {
return fmt.Sprintf("%s-%s", in.Spec.Pipeline, in.Spec.Stage)
}

func (in *CDStageDeploy) IsPending() bool {
return in.Status.Status == CDStageDeployStatusPending
}

func (in *CDStageDeploy) IsInQueue() bool {
return in.Status.Status == CDStageDeployStatusInQueue
}

func (in *CDStageDeploy) IsFailed() bool {
return in.Status.Status == CDStageDeployStatusFailed
}

func (in *CDStageDeploy) IsCompleted() bool {
return in.Status.Status == CDStageDeployStatusCompleted
}

func (in *CDStageDeploy) IsRunning() bool {
return in.Status.Status == CDStageDeployStatusRunning
}

// +kubebuilder:object:root=true

// CDStageDeployList contains a list of CDStageDeploy.
Expand Down
3 changes: 3 additions & 0 deletions api/v1/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@ const (

// CdStageLabel is a label that is used to store the name of the CD stage in the related resources.
CdStageLabel = "app.edp.epam.com/cdstage"

// CdStageDeployLabel is a label that is used to store the name of the CD stage deploy in the related resources.
CdStageDeployLabel = "app.edp.epam.com/cdstagedeploy"
)
4 changes: 4 additions & 0 deletions config/crd/bases/v2.edp.epam.com_cdstagedeployments.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ spec:
stage:
description: Name of related stage
type: string
strategy:
default: Auto
description: TriggerType specifies a strategy for auto-deploy.
type: string
tag:
description: Specifies a latest available tag
properties:
Expand Down
4 changes: 4 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,11 @@ rules:
- pipelineruns
verbs:
- create
- get
- list
- patch
- update
- watch
- apiGroups:
- triggers.tekton.dev
resources:
Expand Down
4 changes: 2 additions & 2 deletions controllers/cdstagedeploy/cdstagedeploy_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func (r *ReconcileCDStageDeploy) SetupWithManager(mgr ctrl.Manager) error {
//+kubebuilder:rbac:groups=v2.edp.epam.com,namespace=placeholder,resources=cdstagedeployments/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=v2.edp.epam.com,namespace=placeholder,resources=cdstagedeployments/finalizers,verbs=update
//+kubebuilder:rbac:groups=triggers.tekton.dev,namespace=placeholder,resources=triggertemplates,verbs=get;list;watch;
//+kubebuilder:rbac:groups=tekton.dev,namespace=placeholder,resources=pipelineruns,verbs=create;list
//+kubebuilder:rbac:groups=tekton.dev,namespace=placeholder,resources=pipelineruns,verbs=get;list;watch;create;update;patch

// Reconcile reads that state of the cluster for a CDStageDeploy object and makes changes based on the state.
func (r *ReconcileCDStageDeploy) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
Expand All @@ -103,7 +103,7 @@ func (r *ReconcileCDStageDeploy) Reconcile(ctx context.Context, request reconcil

oldStatus := stageDeploy.Status.Status

if err := r.chainFactory(r.client).ServeRequest(ctx, stageDeploy); err != nil {
if err := r.chainFactory(r.client, stageDeploy).ServeRequest(ctx, stageDeploy); err != nil {
stageDeploy.SetFailedStatus(err)

if statusErr := r.client.Status().Update(ctx, stageDeploy); statusErr != nil {
Expand Down
6 changes: 3 additions & 3 deletions controllers/cdstagedeploy/cdstagedeploy_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func TestReconcileCDStageDeploy_Reconcile(t *testing.T) {
return fake.NewClientBuilder().WithScheme(scheme).WithObjects(dp).Build()
},
chainFactory: func(t *testing.T) chain.CDStageDeployChain {
return func(cl client.Client) chain.CDStageDeployHandler {
return func(cl client.Client, stageDeploy *codebaseApi.CDStageDeploy) chain.CDStageDeployHandler {
m := mocks.NewMockCDStageDeployHandler(t)

m.On("ServeRequest", mock.Anything, mock.Anything).
Expand Down Expand Up @@ -88,7 +88,7 @@ func TestReconcileCDStageDeploy_Reconcile(t *testing.T) {
return fake.NewClientBuilder().WithScheme(scheme).WithObjects(dp).Build()
},
chainFactory: func(t *testing.T) chain.CDStageDeployChain {
return func(cl client.Client) chain.CDStageDeployHandler {
return func(cl client.Client, stageDeploy *codebaseApi.CDStageDeploy) chain.CDStageDeployHandler {
m := mocks.NewMockCDStageDeployHandler(t)

m.On("ServeRequest", mock.Anything, mock.Anything).
Expand All @@ -115,7 +115,7 @@ func TestReconcileCDStageDeploy_Reconcile(t *testing.T) {
return fake.NewClientBuilder().WithScheme(scheme).Build()
},
chainFactory: func(t *testing.T) chain.CDStageDeployChain {
return func(cl client.Client) chain.CDStageDeployHandler {
return func(cl client.Client, stageDeploy *codebaseApi.CDStageDeploy) chain.CDStageDeployHandler {
m := mocks.NewMockCDStageDeployHandler(t)

m.On("ServeRequest", mock.Anything, mock.Anything).
Expand Down
118 changes: 118 additions & 0 deletions controllers/cdstagedeploy/chain/create_pending_trigger_template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
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
}
Loading