Skip to content

Commit f121111

Browse files
authored
feat: Custom tag for copy container image plugin (#5760)
* wip: changes for v2 * migration * sql script renaming * adding back registryDestinationImageMap in request * migration script update * adding isExposed check * modification for multiple plugin in same stage * wip: fixing query * custom tag deactivate fixes * returning err for deactivateUnusedPaths function * filepath fix * changes needed for updated event payload * down migration * updating migration * updating migration
1 parent 3ad88d5 commit f121111

16 files changed

+385
-132
lines changed

internal/sql/repository/CiArtifactRepository.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,9 @@ func (impl CiArtifactRepositoryImpl) GetArtifactsByDataSourceAndComponentId(data
836836

837837
func (impl CiArtifactRepositoryImpl) FindCiArtifactByImagePaths(images []string) ([]CiArtifact, error) {
838838
var ciArtifacts []CiArtifact
839+
if len(images) == 0 {
840+
return nil, nil
841+
}
839842
err := impl.dbConnection.
840843
Model(&ciArtifacts).
841844
Where(" image in (?) ", pg.In(images)).

internal/sql/repository/CustomTagRepository.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ type ImageTagRepository interface {
5656
DeactivateImagePathReservationByImagePaths(tx *pg.Tx, imagePaths []string) error
5757
DeactivateImagePathReservationByImagePathReservationIds(tx *pg.Tx, imagePathReservationIds []int) error
5858
DisableCustomTag(entityKey int, entityValue string) error
59+
GetImagePathsByIds(ids []int) ([]*ImagePathReservation, error)
5960
}
6061

6162
type ImageTagRepositoryImpl struct {
@@ -139,6 +140,9 @@ func (impl *ImageTagRepositoryImpl) InsertImagePath(tx *pg.Tx, reservation *Imag
139140
}
140141

141142
func (impl *ImageTagRepositoryImpl) DeactivateImagePathReservationByImagePaths(tx *pg.Tx, imagePaths []string) error {
143+
if len(imagePaths) == 0 {
144+
return nil
145+
}
142146
query := `UPDATE image_path_reservation set active=false where image_path in (?)`
143147
_, err := tx.Exec(query, pg.In(imagePaths))
144148
if err != nil && err != pg.ErrNoRows {
@@ -161,3 +165,13 @@ func (impl *ImageTagRepositoryImpl) DisableCustomTag(entityKey int, entityValue
161165
_, err := impl.dbConnection.Exec(query, entityKey, entityValue)
162166
return err
163167
}
168+
func (impl *ImageTagRepositoryImpl) GetImagePathsByIds(ids []int) ([]*ImagePathReservation, error) {
169+
var imagePaths []*ImagePathReservation
170+
if len(ids) == 0 {
171+
return imagePaths, nil
172+
}
173+
err := impl.dbConnection.Model(&imagePaths).
174+
Where("id in (?) ", pg.In(ids)).
175+
Where("active = ?", true).Select()
176+
return imagePaths, err
177+
}

pkg/deployment/trigger/devtronApps/PostStageTriggerService.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ func (impl *TriggerServiceImpl) TriggerPostStage(request bean.TriggerRequest) er
9292
cdStageWorkflowRequest.Type = bean3.CD_WORKFLOW_PIPELINE_TYPE
9393
// handling plugin specific logic
9494

95-
pluginImagePathReservationIds, err := impl.SetCopyContainerImagePluginDataInWorkflowRequest(cdStageWorkflowRequest, pipeline.Id, types.POST, cdWf.CiArtifact)
95+
pluginImagePathReservationIds, err := impl.setCopyContainerImagePluginDataAndReserveImages(cdStageWorkflowRequest, pipeline.Id, types.POST, cdWf.CiArtifact)
9696
if err != nil {
9797
runner.Status = pipelineConfig.WorkflowFailed
9898
runner.Message = err.Error()

pkg/deployment/trigger/devtronApps/PreStageTriggerService.go

Lines changed: 111 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import (
3939
repository3 "github.com/devtron-labs/devtron/pkg/pipeline/history/repository"
4040
"github.com/devtron-labs/devtron/pkg/pipeline/types"
4141
"github.com/devtron-labs/devtron/pkg/plugin"
42+
bean3 "github.com/devtron-labs/devtron/pkg/plugin/bean"
4243
"github.com/devtron-labs/devtron/pkg/resourceQualifiers"
4344
"github.com/devtron-labs/devtron/pkg/sql"
4445
util3 "github.com/devtron-labs/devtron/pkg/util"
@@ -110,7 +111,7 @@ func (impl *TriggerServiceImpl) TriggerPreStage(request bean.TriggerRequest) err
110111
}
111112
cdStageWorkflowRequest.StageType = types.PRE
112113
// handling copyContainerImage plugin specific logic
113-
imagePathReservationIds, err := impl.SetCopyContainerImagePluginDataInWorkflowRequest(cdStageWorkflowRequest, pipeline.Id, types.PRE, artifact)
114+
imagePathReservationIds, err := impl.setCopyContainerImagePluginDataAndReserveImages(cdStageWorkflowRequest, pipeline.Id, types.PRE, artifact)
114115
if err != nil {
115116
runner.Status = pipelineConfig.WorkflowFailed
116117
runner.Message = err.Error()
@@ -236,95 +237,121 @@ func (impl *TriggerServiceImpl) checkVulnerabilityStatusAndFailWfIfNeeded(ctx co
236237
return nil
237238
}
238239

239-
func (impl *TriggerServiceImpl) SetCopyContainerImagePluginDataInWorkflowRequest(cdStageWorkflowRequest *types.WorkflowRequest, pipelineId int, pipelineStage string, artifact *repository.CiArtifact) ([]int, error) {
240-
copyContainerImagePluginId, err := impl.globalPluginService.GetRefPluginIdByRefPluginName(pipeline.COPY_CONTAINER_IMAGE)
241-
var imagePathReservationIds []int
240+
// setCopyContainerImagePluginDataAndReserveImages sets required fields in cdStageWorkflowRequest and reserve images generated by plugin
241+
func (impl *TriggerServiceImpl) setCopyContainerImagePluginDataAndReserveImages(cdStageWorkflowRequest *types.WorkflowRequest, pipelineId int, pipelineStage string, artifact *repository.CiArtifact) ([]int, error) {
242+
243+
copyContainerImagePluginDetail, err := impl.globalPluginService.GetRefPluginIdByRefPluginName(pipeline.COPY_CONTAINER_IMAGE)
242244
if err != nil && err != pg.ErrNoRows {
243245
impl.logger.Errorw("error in getting copyContainerImage plugin id", "err", err)
244-
return imagePathReservationIds, err
246+
return nil, err
245247
}
246-
for _, step := range cdStageWorkflowRequest.PrePostDeploySteps {
247-
if copyContainerImagePluginId != 0 && step.RefPluginId == copyContainerImagePluginId {
248-
var pipelineStageEntityType int
249-
if pipelineStage == types.PRE {
250-
pipelineStageEntityType = pipelineConfigBean.EntityTypePreCD
251-
} else {
252-
pipelineStageEntityType = pipelineConfigBean.EntityTypePostCD
253-
}
254-
customTagId := -1
255-
var DockerImageTag string
256248

257-
customTag, err := impl.customTagService.GetActiveCustomTagByEntityKeyAndValue(pipelineStageEntityType, strconv.Itoa(pipelineId))
258-
if err != nil && err != pg.ErrNoRows {
259-
impl.logger.Errorw("error in fetching custom tag data", "err", err)
260-
return imagePathReservationIds, err
261-
}
249+
pluginIdToVersionMap := make(map[int]string)
250+
for _, p := range copyContainerImagePluginDetail {
251+
pluginIdToVersionMap[p.Id] = p.Version
252+
}
262253

263-
if !customTag.Enabled {
264-
// case when custom tag is not configured - source image tag will be taken as docker image tag
265-
pluginTriggerImageSplit := strings.Split(artifact.Image, ":")
266-
DockerImageTag = pluginTriggerImageSplit[len(pluginTriggerImageSplit)-1]
267-
} else {
268-
// for copyContainerImage plugin parse destination images and save its data in image path reservation table
269-
customTagDbObject, customDockerImageTag, err := impl.customTagService.GetCustomTag(pipelineStageEntityType, strconv.Itoa(pipelineId))
270-
if err != nil && err != pg.ErrNoRows {
271-
impl.logger.Errorw("error in fetching custom tag by entity key and value for CD", "err", err)
272-
return imagePathReservationIds, err
273-
}
274-
if customTagDbObject != nil && customTagDbObject.Id > 0 {
275-
customTagId = customTagDbObject.Id
276-
}
277-
DockerImageTag = customDockerImageTag
278-
}
254+
dockerImageTag, customTagId, err := impl.getDockerTagAndCustomTagIdForPlugin(pipelineStage, pipelineId, artifact)
255+
if err != nil {
256+
impl.logger.Errorw("error in getting docker tag", "err", err)
257+
return nil, err
258+
}
279259

280-
var sourceDockerRegistryId string
281-
if artifact.DataSource == repository.PRE_CD || artifact.DataSource == repository.POST_CD || artifact.DataSource == repository.POST_CI {
282-
if artifact.CredentialsSourceType == repository.GLOBAL_CONTAINER_REGISTRY {
283-
sourceDockerRegistryId = artifact.CredentialSourceValue
284-
}
285-
} else {
286-
sourceDockerRegistryId = cdStageWorkflowRequest.DockerRegistryId
287-
}
288-
registryDestinationImageMap, registryCredentialMap, err := impl.pluginInputVariableParser.HandleCopyContainerImagePluginInputVariables(step.InputVars, DockerImageTag, cdStageWorkflowRequest.CiArtifactDTO.Image, sourceDockerRegistryId)
260+
var sourceDockerRegistryId string
261+
if artifact.DataSource == repository.PRE_CD || artifact.DataSource == repository.POST_CD || artifact.DataSource == repository.POST_CI {
262+
if artifact.CredentialsSourceType == repository.GLOBAL_CONTAINER_REGISTRY {
263+
sourceDockerRegistryId = artifact.CredentialSourceValue
264+
}
265+
} else {
266+
sourceDockerRegistryId = cdStageWorkflowRequest.DockerRegistryId
267+
}
268+
269+
registryCredentialMap := make(map[string]bean3.RegistryCredentials)
270+
var allDestinationImages []string //saving all images to be reserved in this array
271+
272+
for _, step := range cdStageWorkflowRequest.PrePostDeploySteps {
273+
if version, ok := pluginIdToVersionMap[step.RefPluginId]; ok {
274+
registryDestinationImageMap, credentialMap, err := impl.pluginInputVariableParser.HandleCopyContainerImagePluginInputVariables(step.InputVars, dockerImageTag, cdStageWorkflowRequest.CiArtifactDTO.Image, sourceDockerRegistryId)
289275
if err != nil {
290276
impl.logger.Errorw("error in parsing copyContainerImage input variable", "err", err)
291-
return imagePathReservationIds, err
292-
}
293-
var destinationImages []string
294-
for _, images := range registryDestinationImageMap {
295-
for _, image := range images {
296-
destinationImages = append(destinationImages, image)
297-
}
298-
}
299-
// fetch already saved artifacts to check if they are already present
300-
savedCIArtifacts, err := impl.ciArtifactRepository.FindCiArtifactByImagePaths(destinationImages)
301-
if err != nil {
302-
impl.logger.Errorw("error in fetching artifacts by image path", "err", err)
303-
return imagePathReservationIds, err
277+
return nil, err
304278
}
305-
if len(savedCIArtifacts) > 0 {
306-
// if already present in ci artifact, return "image path already in use error"
307-
return imagePathReservationIds, pipelineConfigBean.ErrImagePathInUse
279+
if version == pipeline.COPY_CONTAINER_IMAGE_VERSION_V1 {
280+
// this is needed in ci runner only for v1
281+
cdStageWorkflowRequest.RegistryDestinationImageMap = registryDestinationImageMap
308282
}
309-
imagePathReservationIds, err = impl.ReserveImagesGeneratedAtPlugin(customTagId, registryDestinationImageMap)
310-
if err != nil {
311-
impl.logger.Errorw("error in reserving image", "err", err)
312-
return imagePathReservationIds, err
283+
for _, images := range registryDestinationImageMap {
284+
allDestinationImages = append(allDestinationImages, images...)
313285
}
314-
cdStageWorkflowRequest.RegistryDestinationImageMap = registryDestinationImageMap
315-
cdStageWorkflowRequest.RegistryCredentialMap = registryCredentialMap
316-
var pluginArtifactStage string
317-
if pipelineStage == types.PRE {
318-
pluginArtifactStage = repository.PRE_CD
319-
} else {
320-
pluginArtifactStage = repository.POST_CD
286+
for k, v := range credentialMap {
287+
registryCredentialMap[k] = v
321288
}
322-
cdStageWorkflowRequest.PluginArtifactStage = pluginArtifactStage
323289
}
324290
}
291+
292+
// set data in cdStageWorkflowRequest needed for copy container image plugin
293+
294+
cdStageWorkflowRequest.RegistryCredentialMap = registryCredentialMap
295+
cdStageWorkflowRequest.DockerImageTag = dockerImageTag
296+
if pipelineStage == types.PRE {
297+
cdStageWorkflowRequest.PluginArtifactStage = repository.PRE_CD
298+
} else {
299+
cdStageWorkflowRequest.PluginArtifactStage = repository.POST_CD
300+
}
301+
302+
// fetch already saved artifacts to check if they are already present
303+
304+
savedCIArtifacts, err := impl.ciArtifactRepository.FindCiArtifactByImagePaths(allDestinationImages)
305+
if err != nil {
306+
impl.logger.Errorw("error in fetching artifacts by image path", "err", err)
307+
return nil, err
308+
}
309+
if len(savedCIArtifacts) > 0 {
310+
// if already present in ci artifact, return "image path already in use error"
311+
return nil, pipelineConfigBean.ErrImagePathInUse
312+
}
313+
// reserve all images where data will be
314+
imagePathReservationIds, err := impl.ReserveImagesGeneratedAtPlugin(customTagId, allDestinationImages)
315+
if err != nil {
316+
impl.logger.Errorw("error in reserving image", "err", err)
317+
return imagePathReservationIds, err
318+
}
325319
return imagePathReservationIds, nil
326320
}
327321

322+
func (impl *TriggerServiceImpl) getDockerTagAndCustomTagIdForPlugin(pipelineStage string, pipelineId int, artifact *repository.CiArtifact) (string, int, error) {
323+
var pipelineStageEntityType int
324+
if pipelineStage == types.PRE {
325+
pipelineStageEntityType = pipelineConfigBean.EntityTypePreCD
326+
} else {
327+
pipelineStageEntityType = pipelineConfigBean.EntityTypePostCD
328+
}
329+
customTag, err := impl.customTagService.GetActiveCustomTagByEntityKeyAndValue(pipelineStageEntityType, strconv.Itoa(pipelineId))
330+
if err != nil && err != pg.ErrNoRows {
331+
impl.logger.Errorw("error in fetching custom tag data", "err", err)
332+
return "", 0, err
333+
}
334+
var DockerImageTag string
335+
customTagId := -1 // if customTag is not configured id=-1 will be saved in image_path_reservation table for image reservation
336+
if !customTag.Enabled {
337+
// case when custom tag is not configured - source image tag will be taken as docker image tag
338+
pluginTriggerImageSplit := strings.Split(artifact.Image, ":")
339+
DockerImageTag = pluginTriggerImageSplit[len(pluginTriggerImageSplit)-1]
340+
} else {
341+
// for copyContainerImage plugin parse destination images and save its data in image path reservation table
342+
customTagDbObject, customDockerImageTag, err := impl.customTagService.GetCustomTag(pipelineStageEntityType, strconv.Itoa(pipelineId))
343+
if err != nil && err != pg.ErrNoRows {
344+
impl.logger.Errorw("error in fetching custom tag by entity key and value for CD", "err", err)
345+
return "", 0, err
346+
}
347+
if customTagDbObject != nil && customTagDbObject.Id > 0 {
348+
customTagId = customTagDbObject.Id
349+
}
350+
DockerImageTag = customDockerImageTag
351+
}
352+
return DockerImageTag, customTagId, nil
353+
}
354+
328355
func (impl *TriggerServiceImpl) buildWFRequest(runner *pipelineConfig.CdWorkflowRunner, cdWf *pipelineConfig.CdWorkflow, cdPipeline *pipelineConfig.Pipeline, envDeploymentConfig *bean5.DeploymentConfig, triggeredBy int32) (*types.WorkflowRequest, error) {
329356
if cdPipeline.App.Id == 0 {
330357
appModel, err := impl.appRepository.FindById(cdPipeline.AppId)
@@ -843,20 +870,20 @@ func (impl *TriggerServiceImpl) getSourceCiPipelineForArtifact(ciPipeline pipeli
843870
return sourceCiPipeline, nil
844871
}
845872

846-
func (impl *TriggerServiceImpl) ReserveImagesGeneratedAtPlugin(customTagId int, registryImageMap map[string][]string) ([]int, error) {
873+
func (impl *TriggerServiceImpl) ReserveImagesGeneratedAtPlugin(customTagId int, destinationImages []string) ([]int, error) {
847874
var imagePathReservationIds []int
848-
for _, images := range registryImageMap {
849-
for _, image := range images {
850-
imagePathReservationData, err := impl.customTagService.ReserveImagePath(image, customTagId)
851-
if err != nil {
852-
impl.logger.Errorw("Error in marking custom tag reserved", "err", err)
853-
return imagePathReservationIds, err
854-
}
855-
if imagePathReservationData != nil {
856-
imagePathReservationIds = append(imagePathReservationIds, imagePathReservationData.Id)
857-
}
875+
876+
for _, image := range destinationImages {
877+
imagePathReservationData, err := impl.customTagService.ReserveImagePath(image, customTagId)
878+
if err != nil {
879+
impl.logger.Errorw("Error in marking custom tag reserved", "err", err)
880+
return imagePathReservationIds, err
881+
}
882+
if imagePathReservationData != nil {
883+
imagePathReservationIds = append(imagePathReservationIds, imagePathReservationData.Id)
858884
}
859885
}
886+
860887
return imagePathReservationIds, nil
861888
}
862889

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package bean
2+
3+
import (
4+
"slices"
5+
"time"
6+
)
7+
8+
type Kind string
9+
type CredentialSourceType string
10+
type ArtifactType string
11+
12+
const (
13+
PluginArtifactsKind Kind = "PluginArtifacts"
14+
GlobalContainerRegistrySource CredentialSourceType = "global_container_registry"
15+
ArtifactTypeContainer ArtifactType = "CONTAINER"
16+
)
17+
18+
type PluginArtifacts struct {
19+
Kind Kind `json:"Kind"`
20+
Artifacts []Artifact `json:"Artifacts"`
21+
}
22+
23+
func NewPluginArtifact() *PluginArtifacts {
24+
return &PluginArtifacts{
25+
Kind: PluginArtifactsKind,
26+
Artifacts: make([]Artifact, 0),
27+
}
28+
}
29+
30+
func (p *PluginArtifacts) MergePluginArtifact(pluginArtifact *PluginArtifacts) {
31+
if pluginArtifact == nil {
32+
return
33+
}
34+
p.Artifacts = append(p.Artifacts, pluginArtifact.Artifacts...)
35+
}
36+
37+
func (p *PluginArtifacts) GetRegistryToUniqueContainerArtifactDataMapping() map[string][]string {
38+
registryToImageMapping := make(map[string][]string)
39+
for _, artifact := range p.Artifacts {
40+
if artifact.Type == ArtifactTypeContainer {
41+
if artifact.CredentialsSourceType == GlobalContainerRegistrySource {
42+
if _, ok := registryToImageMapping[artifact.CredentialSourceValue]; !ok {
43+
registryToImageMapping[artifact.CredentialSourceValue] = make([]string, 0)
44+
}
45+
registryToImageMapping[artifact.CredentialSourceValue] = append(registryToImageMapping[artifact.CredentialSourceValue], artifact.Data...)
46+
slices.Sort(registryToImageMapping[artifact.CredentialSourceValue])
47+
slices.Compact(registryToImageMapping[artifact.CredentialSourceValue])
48+
}
49+
}
50+
}
51+
return registryToImageMapping
52+
}
53+
54+
type Artifact struct {
55+
Type ArtifactType `json:"Type"`
56+
Data []string `json:"Data"`
57+
CredentialsSourceType CredentialSourceType `json:"CredentialsSourceType"`
58+
CredentialSourceValue string `json:"CredentialSourceValue"`
59+
CreatedByPluginIdentifier string `json:"createdByPluginIdentifier"`
60+
CreatedOn time.Time `json:"createdOn"`
61+
}

pkg/eventProcessor/bean/workflowEventBean.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ type CdStageCompleteEvent struct {
3838
PipelineName string `json:"pipelineName"`
3939
CiArtifactDTO pipelineConfig.CiArtifactDTO `json:"ciArtifactDTO"`
4040
PluginRegistryArtifactDetails map[string][]string `json:"PluginRegistryArtifactDetails"`
41+
PluginArtifacts *PluginArtifacts `json:"pluginArtifacts"`
4142
}
4243

4344
type UserDeploymentRequest struct {
@@ -81,6 +82,7 @@ type CiCompleteEvent struct {
8182
PluginRegistryArtifactDetails map[string][]string `json:"PluginRegistryArtifactDetails"`
8283
PluginArtifactStage string `json:"pluginArtifactStage"`
8384
pluginImageDetails *registry.ImageDetailsFromCR
85+
PluginArtifacts *PluginArtifacts `json:"pluginArtifacts"`
8486
}
8587

8688
func (c *CiCompleteEvent) GetPluginImageDetails() *registry.ImageDetailsFromCR {

0 commit comments

Comments
 (0)