From 8e985794f6611c66a07e32f3e5e94f65b2d0ac92 Mon Sep 17 00:00:00 2001 From: rafi Date: Mon, 15 Jun 2026 14:48:51 +0200 Subject: [PATCH 01/10] add create and delete endpoints for projects and assets Signed-off-by: rafi --- controllers/project_controller.go | 183 +++++++++++++++++++- database/repositories/project_repository.go | 47 +++++ router/project_router.go | 3 + services/asset_service.go | 22 +++ services/project_service.go | 26 +++ shared/common_interfaces.go | 3 + 6 files changed, 275 insertions(+), 9 deletions(-) diff --git a/controllers/project_controller.go b/controllers/project_controller.go index aa94c5024..7b1e3bef0 100644 --- a/controllers/project_controller.go +++ b/controllers/project_controller.go @@ -19,9 +19,12 @@ import ( "encoding/json" "fmt" "io" + "log/slog" + cdx "github.com/CycloneDX/cyclonedx-go" "github.com/l3montree-dev/devguard/database/models" "github.com/l3montree-dev/devguard/dtos" + "github.com/l3montree-dev/devguard/normalize" "github.com/l3montree-dev/devguard/shared" "github.com/l3montree-dev/devguard/transformer" "github.com/l3montree-dev/devguard/utils" @@ -30,18 +33,28 @@ import ( ) type ProjectController struct { - projectRepository shared.ProjectRepository - assetRepository shared.AssetRepository - projectService shared.ProjectService - webhookRepository shared.WebhookIntegrationRepository + projectRepository shared.ProjectRepository + assetRepository shared.AssetRepository + assetVersionRepository shared.AssetVersionRepository + artifactRepository shared.ArtifactRepository + assetVersionService shared.AssetVersionService + assetService shared.AssetService + projectService shared.ProjectService + webhookRepository shared.WebhookIntegrationRepository + scanService shared.ScanService } -func NewProjectController(repository shared.ProjectRepository, assetRepository shared.AssetRepository, projectService shared.ProjectService, webhookRepository shared.WebhookIntegrationRepository) *ProjectController { +func NewProjectController(repository shared.ProjectRepository, assetRepository shared.AssetRepository, assetVersionRepository shared.AssetVersionRepository, artifactRepository shared.ArtifactRepository, assetVersionService shared.AssetVersionService, assetService shared.AssetService, projectService shared.ProjectService, webhookRepository shared.WebhookIntegrationRepository, scanService shared.ScanService) *ProjectController { return &ProjectController{ - projectRepository: repository, - assetRepository: assetRepository, - projectService: projectService, - webhookRepository: webhookRepository, + projectRepository: repository, + assetRepository: assetRepository, + assetVersionRepository: assetVersionRepository, + artifactRepository: artifactRepository, + assetVersionService: assetVersionService, + assetService: assetService, + projectService: projectService, + webhookRepository: webhookRepository, + scanService: scanService, } } @@ -507,3 +520,155 @@ func (ProjectController *ProjectController) UpdateConfigFile(ctx shared.Context) } return ctx.String(200, configContent) } + +type dynamicProjectRequest struct { + Verb string `json:"verb"` + ProjectName string `json:"projectName"` + AssetName string `json:"assetName"` + AssetVersion string `json:"assetVersion"` + Sbom json.RawMessage `json:"sbom,omitempty"` +} + +func (ProjectController *ProjectController) HandleDynamicProject(ctx shared.Context) error { + + body, err := io.ReadAll(ctx.Request().Body) + if err != nil { + return echo.NewHTTPError(400, fmt.Sprintf("could not read request body: %s", err.Error())).WithInternal(err) + } + + var probe dynamicProjectRequest + if err := json.Unmarshal(body, &probe); err != nil { + return echo.NewHTTPError(400, fmt.Sprintf("could not parse request body: %s", err.Error())).WithInternal(err) + } + + if probe.ProjectName == "" || probe.AssetName == "" { + return echo.NewHTTPError(400, "verb, projectName, and assetName are required") + } + + action := probe.Verb + projectName := probe.ProjectName + assetName := probe.AssetName + assetVersionName := probe.AssetVersion + + organization := shared.GetOrg(ctx) + parentProject := shared.GetProject(ctx) + userID := shared.GetSession(ctx).GetUserID() + + if action == "delete" { + err := ProjectController.projectRepository.CleanupDynamicProject(ctx.Request().Context(), nil, organization.GetID(), parentProject.ID, projectName, assetName, assetVersionName) + if err != nil { + return echo.NewHTTPError(500, fmt.Sprintf("could not delete project: %s", err.Error())).WithInternal(err) + } + + return ctx.JSON(200, map[string]string{"message": "project and asset deleted successfully"}) + } else if action != "update" { + return echo.NewHTTPError(400, "invalid verb, only 'update' and 'delete' are allowed") + } + + bom := new(cdx.BOM) + + if probe.Sbom == nil { + return echo.NewHTTPError(400, "sbom is required") + } + if err := json.Unmarshal(probe.Sbom, bom); err != nil { + return echo.NewHTTPError(400, fmt.Sprintf("could not parse CycloneDX BOM: %s", err.Error())).WithInternal(err) + } + + project, err := ProjectController.projectService.FindOrCreateProject(ctx, organization.GetID(), projectName, parentProject.ID) + + rbac := shared.GetRBAC(ctx) + asset, err := ProjectController.assetService.FindOrCreateAsset(ctx.Request().Context(), rbac, organization.GetID(), project.ID, assetName, userID) + if err != nil { + return echo.NewHTTPError(500, fmt.Sprintf("could not create asset: %s", err.Error())).WithInternal(err) + } + + assetVersion, err := ProjectController.assetVersionRepository.FindOrCreate(ctx.Request().Context(), nil, assetVersionName, asset.ID, false, nil) + if err != nil { + return echo.NewHTTPError(500, fmt.Sprintf("could not create asset version: %s", err.Error())).WithInternal(err) + } + + artifactName := normalize.ArtifactPurl("operator", fmt.Sprintf("%s/%s/%s", organization.Slug, project.Slug, asset.Name)) + + artifact := models.Artifact{ + ArtifactName: artifactName, + AssetVersionName: assetVersion.Name, + AssetID: asset.ID, + } + if err := ProjectController.artifactRepository.Save(ctx.Request().Context(), nil, &artifact); err != nil { + slog.Error("trivy operator: could not save artifact", "err", err) + return ctx.JSON(500, map[string]string{"error": "could not save artifact"}) + } + + normalized, err := normalize.SBOMGraphFromCycloneDX(bom, artifactName, "operator", asset.KeepOriginalSbomRootComponent) + if err != nil { + slog.Error("trivy operator: failed to normalize BOM", "err", err) + return ctx.JSON(400, map[string]string{"error": "could not normalize SBOM"}) + } + + wholeSBOM, err := ProjectController.assetVersionService.UpdateSBOM(ctx.Request().Context(), nil, organization, *project, *asset, assetVersion, artifactName, normalized) + if err != nil { + slog.Error("trivy operator: could not update SBOM", "err", err) + return ctx.JSON(500, map[string]string{"error": "could not update SBOM"}) + } + + tx := ProjectController.artifactRepository.GetDB(ctx.Request().Context(), nil).Begin() + defer tx.Rollback() + + userAgent := ctx.Request().UserAgent() + _, _, _, err = ProjectController.scanService.ScanNormalizedSBOM(ctx.Request().Context(), tx, organization, *project, *asset, assetVersion, artifact, wholeSBOM, userID, &userAgent) + if err != nil { + slog.Error("trivy operator: scan failed", "err", err) + return ctx.JSON(500, map[string]string{"error": "scan failed"}) + } + + tx.Commit() + return ctx.JSON(200, map[string]string{"message": "project and asset created, SBOM processed and scan started successfully"}) +} + +type devGuardAsset struct { + ProjectName string `json:"projectName"` + Assets []struct { + Name string `json:"name"` + Versions []string `json:"versions"` + } `json:"assets"` +} + +func (ProjectController *ProjectController) ListDynamicProjects(ctx shared.Context) error { + reqCtx := ctx.Request().Context() + parentProject := shared.GetProject(ctx) + + //TOD:: we should optimize this by doing it in a single query. + projects, err := ProjectController.projectRepository.GetDirectChildProjects(reqCtx, nil, parentProject.ID) + if err != nil { + return echo.NewHTTPError(500, "could not list projects").WithInternal(err) + } + + result := make([]devGuardAsset, 0, len(projects)) + for _, project := range projects { + assets, err := ProjectController.assetRepository.GetByProjectID(reqCtx, nil, project.ID) + if err != nil { + return echo.NewHTTPError(500, "could not list assets").WithInternal(err) + } + + entry := devGuardAsset{ProjectName: project.Name} + for _, asset := range assets { + versions, err := ProjectController.assetVersionRepository.GetAssetVersionsByAssetID(reqCtx, nil, asset.ID) + if err != nil { + return echo.NewHTTPError(500, "could not list asset versions").WithInternal(err) + } + + assetEntry := struct { + Name string `json:"name"` + Versions []string `json:"versions"` + }{Name: asset.Name} + + for _, v := range versions { + assetEntry.Versions = append(assetEntry.Versions, v.Name) + } + entry.Assets = append(entry.Assets, assetEntry) + } + result = append(result, entry) + } + + return ctx.JSON(200, result) +} diff --git a/database/repositories/project_repository.go b/database/repositories/project_repository.go index 9400464e1..38a046ab5 100644 --- a/database/repositories/project_repository.go +++ b/database/repositories/project_repository.go @@ -591,3 +591,50 @@ func (g *projectRepository) Upsert(ctx context.Context, tx *gorm.DB, t *[]*model return g.GetDB(ctx, tx).Clauses(clause.OnConflict{UpdateAll: true, Columns: conflictingColumns}).Create(t).Error } + +func (g *projectRepository) CleanupDynamicProject(ctx context.Context, tx *gorm.DB, organizationID uuid.UUID, parentProjectID uuid.UUID, projectName string, assetName string, assetVersionName string) error { + query := ` +WITH + target AS ( + SELECT + p.id AS project_id, + a.id AS asset_id, + av.name AS asset_version_name + FROM projects p + JOIN assets a ON a.project_id = p.id AND a.name = ? + JOIN asset_versions av ON av.asset_id = a.id AND av.name = ? + WHERE p.organization_id = ? + AND p.parent_id = ? + AND p.name = ? + LIMIT 1 + ), + del_asset_version AS ( + DELETE FROM asset_versions + WHERE asset_id = (SELECT asset_id FROM target) + AND name = (SELECT asset_version_name FROM target) + ), + del_asset AS ( + DELETE FROM assets + WHERE id = (SELECT asset_id FROM target) + AND NOT EXISTS ( + SELECT 1 FROM asset_versions + WHERE asset_id = (SELECT asset_id FROM target) + AND name != (SELECT asset_version_name FROM target) + ) + ), + del_project AS ( + DELETE FROM projects + WHERE id = (SELECT project_id FROM target) + AND NOT EXISTS ( + SELECT 1 FROM assets + WHERE project_id = (SELECT project_id FROM target) + AND id != (SELECT asset_id FROM target) + ) + ) +SELECT 1` + + return g.GetDB(ctx, tx).Exec(query, + assetName, assetVersionName, + organizationID, parentProjectID, projectName, + ).Error +} diff --git a/router/project_router.go b/router/project_router.go index d3f6ba6b8..d1bd346c0 100644 --- a/router/project_router.go +++ b/router/project_router.go @@ -68,6 +68,9 @@ func NewProjectRouter( projectRouter.GET("/releases/", releaseController.List) projectRouter.GET("/components/", componentController.SearchComponentOccurrences, projectScopedRBAC(shared.ObjectAsset, shared.ActionCreate)) + projectRouter.POST("/dynamic-project/", projectController.HandleDynamicProject, middlewares.NeededScope([]string{"manage"})) + projectRouter.GET("/dynamic-project/", projectController.ListDynamicProjects, middlewares.NeededScope([]string{"manage"})) + projectRouter.POST("/assets/", assetController.Create, middlewares.NeededScope([]string{"manage"}), projectScopedRBAC(shared.ObjectAsset, shared.ActionCreate)) projectUpdateAccessControlRequired := projectRouter.Group("", middlewares.NeededScope([]string{"manage"}), projectScopedRBAC(shared.ObjectProject, shared.ActionUpdate)) diff --git a/services/asset_service.go b/services/asset_service.go index 9330a980b..5d89f9e23 100644 --- a/services/asset_service.go +++ b/services/asset_service.go @@ -20,12 +20,14 @@ import ( "log/slog" "github.com/google/uuid" + "github.com/gosimple/slug" "github.com/l3montree-dev/devguard/database/models" "github.com/l3montree-dev/devguard/dtos" "github.com/l3montree-dev/devguard/shared" "github.com/l3montree-dev/devguard/utils" "github.com/labstack/echo/v4" "github.com/ory/client-go" + "github.com/pkg/errors" "gorm.io/gorm" ) @@ -45,6 +47,26 @@ func NewAssetService(assetRepository shared.AssetRepository, dependencyVulnRepos } } +func (s *assetService) FindOrCreateAsset(ctx context.Context, rbac shared.AccessControl, orgID uuid.UUID, projectID uuid.UUID, name string, currentUser string) (*models.Asset, error) { + slug := slug.Make(name) + asset, err := s.assetRepository.ReadBySlug(ctx, nil, projectID, slug) + if err == nil { + return &asset, nil + } + if !errors.Is(err, gorm.ErrRecordNotFound) { + return nil, err + } + + newAsset := models.Asset{ + Name: name, + Slug: slug, + ProjectID: projectID, + } + + return s.CreateAsset(ctx, rbac, currentUser, newAsset) + +} + func (s *assetService) CreateAsset(ctx context.Context, rbac shared.AccessControl, currentUser string, asset models.Asset) (*models.Asset, error) { newAsset := asset if newAsset.Name == "" || newAsset.Slug == "" { diff --git a/services/project_service.go b/services/project_service.go index be2575273..6ae1c96bd 100644 --- a/services/project_service.go +++ b/services/project_service.go @@ -5,11 +5,14 @@ import ( "log/slog" "github.com/google/uuid" + "github.com/gosimple/slug" "github.com/l3montree-dev/devguard/database" "github.com/l3montree-dev/devguard/database/models" "github.com/l3montree-dev/devguard/dtos" "github.com/l3montree-dev/devguard/shared" "github.com/labstack/echo/v4" + "github.com/pkg/errors" + "gorm.io/gorm" ) type projectService struct { @@ -36,6 +39,29 @@ func (s *projectService) ReadBySlug(ctx shared.Context, organizationID uuid.UUID return project, nil } +func (s *projectService) FindOrCreateProject(ctx shared.Context, orgID uuid.UUID, name string, parentID uuid.UUID) (*models.Project, error) { + slug := slug.Make(name) + project, err := s.projectRepository.ReadBySlug(ctx.Request().Context(), nil, orgID, slug) + if err == nil { + return &project, nil + } + if !errors.Is(err, gorm.ErrRecordNotFound) { + return nil, err + } + + newProject := &models.Project{ + Name: name, + Slug: slug, + OrganizationID: orgID, + ParentID: &parentID, + } + err = s.CreateProject(ctx, newProject) + if err != nil { + return nil, err + } + return newProject, nil +} + func (s *projectService) CreateProject(ctx shared.Context, project *models.Project) error { newProject := project diff --git a/shared/common_interfaces.go b/shared/common_interfaces.go index c9876d8df..5efdf9eff 100644 --- a/shared/common_interfaces.go +++ b/shared/common_interfaces.go @@ -106,6 +106,7 @@ type ProjectRepository interface { ListSubProjectsAndAssets(ctx context.Context, tx DB, allowedAssetIDs []string, allowedProjectIDs []uuid.UUID, parentID *uuid.UUID, orgID uuid.UUID, pageInfo PageInfo, search string, filter []FilterQuery, sort []SortQuery) (Paged[dtos.ProjectAssetDTO], error) SearchProjectsWithSubProjectsAndAssetsPaged(ctx context.Context, tx DB, allowedAssetIDs []string, allowedProjectIDs []string, parentID *uuid.UUID, orgID uuid.UUID, pageInfo PageInfo, search string, filter []FilterQuery, sort []SortQuery) (Paged[dtos.ProjectDTO], error) All(ctx context.Context, tx DB) ([]models.Project, error) + CleanupDynamicProject(ctx context.Context, tx DB, organizationID uuid.UUID, parentProjectID uuid.UUID, projectName string, assetName string, assetVersionName string) error } type Verifier interface { @@ -375,6 +376,7 @@ type ProjectService interface { CreateProject(ctx Context, project *models.Project) error BootstrapProject(ctx context.Context, rbac AccessControl, project *models.Project) error SearchProjectsWithSubProjectsAndAssetsPaged(c Context) (Paged[dtos.ProjectDTO], error) + FindOrCreateProject(ctx Context, orgID uuid.UUID, name string, parentID uuid.UUID) (*models.Project, error) } type InTotoVerifierService interface { @@ -389,6 +391,7 @@ type AssetService interface { GetCVSSBadgeSVG(ctx context.Context, latest *models.ArtifactRiskHistory) string CreateAsset(ctx context.Context, rbac AccessControl, currentUserID string, asset models.Asset) (*models.Asset, error) BootstrapAsset(ctx context.Context, rbac AccessControl, asset *models.Asset) error + FindOrCreateAsset(ctx context.Context, rbac AccessControl, orgID uuid.UUID, projectID uuid.UUID, name string, currentUser string) (*models.Asset, error) } type ArtifactService interface { GetArtifactsByAssetIDAndAssetVersionName(ctx context.Context, tx DB, assetID uuid.UUID, assetVersionName string) ([]models.Artifact, error) From 0ed7b8d1a3776065a95f9e012fd874122c06501a Mon Sep 17 00:00:00 2001 From: rafi Date: Tue, 16 Jun 2026 11:16:59 +0200 Subject: [PATCH 02/10] update dynamic project handling to use external entity provider ID Signed-off-by: rafi --- controllers/project_controller.go | 55 ++++++++++----------- database/models/project_model.go | 5 +- database/repositories/asset_repository.go | 33 +++++++++++++ database/repositories/project_repository.go | 1 + dtos/project_dto.go | 16 ++++++ router/project_router.go | 4 +- services/asset_service.go | 31 +++++++----- services/project_service.go | 40 ++++++++------- shared/common_interfaces.go | 6 ++- transformer/project_transformer.go | 3 +- 10 files changed, 124 insertions(+), 70 deletions(-) diff --git a/controllers/project_controller.go b/controllers/project_controller.go index 7b1e3bef0..e1143b4a7 100644 --- a/controllers/project_controller.go +++ b/controllers/project_controller.go @@ -20,6 +20,7 @@ import ( "fmt" "io" "log/slog" + "strings" cdx "github.com/CycloneDX/cyclonedx-go" "github.com/l3montree-dev/devguard/database/models" @@ -521,14 +522,6 @@ func (ProjectController *ProjectController) UpdateConfigFile(ctx shared.Context) return ctx.String(200, configContent) } -type dynamicProjectRequest struct { - Verb string `json:"verb"` - ProjectName string `json:"projectName"` - AssetName string `json:"assetName"` - AssetVersion string `json:"assetVersion"` - Sbom json.RawMessage `json:"sbom,omitempty"` -} - func (ProjectController *ProjectController) HandleDynamicProject(ctx shared.Context) error { body, err := io.ReadAll(ctx.Request().Body) @@ -536,20 +529,22 @@ func (ProjectController *ProjectController) HandleDynamicProject(ctx shared.Cont return echo.NewHTTPError(400, fmt.Sprintf("could not read request body: %s", err.Error())).WithInternal(err) } - var probe dynamicProjectRequest + var probe dtos.DynamicProjectRequestDTO if err := json.Unmarshal(body, &probe); err != nil { return echo.NewHTTPError(400, fmt.Sprintf("could not parse request body: %s", err.Error())).WithInternal(err) } - if probe.ProjectName == "" || probe.AssetName == "" { - return echo.NewHTTPError(400, "verb, projectName, and assetName are required") + if probe.ProjectExternalEntityID == "" || probe.AssetExternalEntityID == "" { + return echo.NewHTTPError(400, "verb, projectExternalEntityId, and assetExternalEntityId are required") } action := probe.Verb - projectName := probe.ProjectName - assetName := probe.AssetName + projectName := probe.ProjectExternalEntityID + assetName := probe.AssetExternalEntityID assetVersionName := probe.AssetVersion - + providerID := ctx.Param("providerID") + //delete the trailing slash if it exists + providerID = strings.TrimSuffix(providerID, "/") organization := shared.GetOrg(ctx) parentProject := shared.GetProject(ctx) userID := shared.GetSession(ctx).GetUserID() @@ -574,10 +569,10 @@ func (ProjectController *ProjectController) HandleDynamicProject(ctx shared.Cont return echo.NewHTTPError(400, fmt.Sprintf("could not parse CycloneDX BOM: %s", err.Error())).WithInternal(err) } - project, err := ProjectController.projectService.FindOrCreateProject(ctx, organization.GetID(), projectName, parentProject.ID) + project, err := ProjectController.projectService.FindOrCreateProject(ctx, providerID, organization.GetID(), projectName, parentProject.ID) rbac := shared.GetRBAC(ctx) - asset, err := ProjectController.assetService.FindOrCreateAsset(ctx.Request().Context(), rbac, organization.GetID(), project.ID, assetName, userID) + asset, err := ProjectController.assetService.FindOrCreateAsset(ctx.Request().Context(), rbac, providerID, organization.GetID(), project.ID, assetName, userID) if err != nil { return echo.NewHTTPError(500, fmt.Sprintf("could not create asset: %s", err.Error())).WithInternal(err) } @@ -625,42 +620,44 @@ func (ProjectController *ProjectController) HandleDynamicProject(ctx shared.Cont return ctx.JSON(200, map[string]string{"message": "project and asset created, SBOM processed and scan started successfully"}) } -type devGuardAsset struct { - ProjectName string `json:"projectName"` - Assets []struct { - Name string `json:"name"` - Versions []string `json:"versions"` - } `json:"assets"` -} - func (ProjectController *ProjectController) ListDynamicProjects(ctx shared.Context) error { reqCtx := ctx.Request().Context() parentProject := shared.GetProject(ctx) + providerID := ctx.Param("providerID") + //delete the trailing slash if it exists + providerID = strings.TrimSuffix(providerID, "/") + //TOD:: we should optimize this by doing it in a single query. projects, err := ProjectController.projectRepository.GetDirectChildProjects(reqCtx, nil, parentProject.ID) if err != nil { return echo.NewHTTPError(500, "could not list projects").WithInternal(err) } - result := make([]devGuardAsset, 0, len(projects)) + result := make([]dtos.ProjectsAssetAssetVersionsDTO, 0, len(projects)) for _, project := range projects { + if project.ExternalEntityProviderID == nil || *project.ExternalEntityProviderID != providerID { + continue + } assets, err := ProjectController.assetRepository.GetByProjectID(reqCtx, nil, project.ID) if err != nil { return echo.NewHTTPError(500, "could not list assets").WithInternal(err) } - entry := devGuardAsset{ProjectName: project.Name} + entry := dtos.ProjectsAssetAssetVersionsDTO{ProjectExternalEntityID: project.Name} for _, asset := range assets { + if asset.ExternalEntityProviderID == nil || *asset.ExternalEntityProviderID != providerID { + continue + } versions, err := ProjectController.assetVersionRepository.GetAssetVersionsByAssetID(reqCtx, nil, asset.ID) if err != nil { return echo.NewHTTPError(500, "could not list asset versions").WithInternal(err) } assetEntry := struct { - Name string `json:"name"` - Versions []string `json:"versions"` - }{Name: asset.Name} + AssetExternalEntityID string `json:"assetExternalEntityId"` + Versions []string `json:"versions"` + }{AssetExternalEntityID: asset.Name} for _, v := range versions { assetEntry.Versions = append(assetEntry.Versions, v.Name) diff --git a/database/models/project_model.go b/database/models/project_model.go index deec3e8dd..94446cc93 100644 --- a/database/models/project_model.go +++ b/database/models/project_model.go @@ -8,9 +8,8 @@ import ( type ProjectType string const ( - ProjectTypeDefault ProjectType = "default" - ProjectTypeKubernetesNamespace ProjectType = "kubernetesNamespace" - ProjectTypeKubernetesCluster ProjectType = "kubernetesCluster" + ProjectTypeDefault ProjectType = "default" + ProjectTypeDynamic ProjectType = "dynamic" ) type ProjectState string diff --git a/database/repositories/asset_repository.go b/database/repositories/asset_repository.go index 1319dc1a8..ee60817c9 100644 --- a/database/repositories/asset_repository.go +++ b/database/repositories/asset_repository.go @@ -285,3 +285,36 @@ func (repository *assetRepository) GetAssetsWithVulnSharingEnabled(ctx context.C ).Preload("Project").Find(&assets).Error return assets, err } + +func (repository *assetRepository) UpsertSplit(ctx context.Context, tx *gorm.DB, externalProviderID string, assets []*models.Asset) ([]*models.Asset, []*models.Asset, error) { + var existingAssets []models.Asset + err := repository.GetDB(ctx, tx).Where("external_entity_id IN (?) AND external_entity_provider_id = ?", utils.Map(assets, func(a *models.Asset) *string { return a.ExternalEntityID }), externalProviderID).Find(&existingAssets).Error + if err != nil { + return nil, nil, err + } + + existingMap := make(map[string]bool) + for _, a := range existingAssets { + existingMap[*a.ExternalEntityID] = true + } + + err = repository.Upsert(ctx, tx, &assets, []clause.Column{ + {Name: "external_entity_provider_id"}, + {Name: "external_entity_id"}, + }, []string{"name", "description", "project_id", "avatar"}) + if err != nil { + return nil, nil, err + } + + newAssets := make([]*models.Asset, 0) + updatedAssets := make([]*models.Asset, 0) + for _, a := range assets { + if !existingMap[*a.ExternalEntityID] { + newAssets = append(newAssets, a) + } else { + updatedAssets = append(updatedAssets, a) + } + } + + return newAssets, updatedAssets, nil +} diff --git a/database/repositories/project_repository.go b/database/repositories/project_repository.go index 38a046ab5..7a99d175b 100644 --- a/database/repositories/project_repository.go +++ b/database/repositories/project_repository.go @@ -606,6 +606,7 @@ WITH WHERE p.organization_id = ? AND p.parent_id = ? AND p.name = ? + AND p.type = 'dynamic' LIMIT 1 ), del_asset_version AS ( diff --git a/dtos/project_dto.go b/dtos/project_dto.go index b3953492a..3f8e2e9bb 100644 --- a/dtos/project_dto.go +++ b/dtos/project_dto.go @@ -16,6 +16,7 @@ package dtos import ( + "encoding/json" "time" "github.com/google/uuid" @@ -94,3 +95,18 @@ type ProjectAssetDTO struct { SubGroupsAndAssets []ProjectAssetDTO `json:"subGroupsAndAsset" gorm:"-"` } + +type ProjectsAssetAssetVersionsDTO struct { + ProjectExternalEntityID string `json:"projectExternalEntityId"` + Assets []struct { + AssetExternalEntityID string `json:"assetExternalEntityId"` + Versions []string `json:"versions"` + } `json:"assets"` +} +type DynamicProjectRequestDTO struct { + Verb string `json:"verb"` + ProjectExternalEntityID string `json:"projectExternalEntityId"` + AssetExternalEntityID string `json:"assetExternalEntityId"` + AssetVersion string `json:"assetVersion"` + Sbom json.RawMessage `json:"sbom,omitempty"` +} diff --git a/router/project_router.go b/router/project_router.go index d1bd346c0..d5dba3c52 100644 --- a/router/project_router.go +++ b/router/project_router.go @@ -68,8 +68,8 @@ func NewProjectRouter( projectRouter.GET("/releases/", releaseController.List) projectRouter.GET("/components/", componentController.SearchComponentOccurrences, projectScopedRBAC(shared.ObjectAsset, shared.ActionCreate)) - projectRouter.POST("/dynamic-project/", projectController.HandleDynamicProject, middlewares.NeededScope([]string{"manage"})) - projectRouter.GET("/dynamic-project/", projectController.ListDynamicProjects, middlewares.NeededScope([]string{"manage"})) + projectRouter.POST("/dynamic-project/dn/:providerID", projectController.HandleDynamicProject, middlewares.NeededScope([]string{"manage"})) + projectRouter.GET("/dynamic-project/dn/:providerID", projectController.ListDynamicProjects, middlewares.NeededScope([]string{"manage"})) projectRouter.POST("/assets/", assetController.Create, middlewares.NeededScope([]string{"manage"}), projectScopedRBAC(shared.ObjectAsset, shared.ActionCreate)) diff --git a/services/asset_service.go b/services/asset_service.go index 5d89f9e23..57b4ef970 100644 --- a/services/asset_service.go +++ b/services/asset_service.go @@ -27,7 +27,6 @@ import ( "github.com/l3montree-dev/devguard/utils" "github.com/labstack/echo/v4" "github.com/ory/client-go" - "github.com/pkg/errors" "gorm.io/gorm" ) @@ -47,24 +46,30 @@ func NewAssetService(assetRepository shared.AssetRepository, dependencyVulnRepos } } -func (s *assetService) FindOrCreateAsset(ctx context.Context, rbac shared.AccessControl, orgID uuid.UUID, projectID uuid.UUID, name string, currentUser string) (*models.Asset, error) { - slug := slug.Make(name) - asset, err := s.assetRepository.ReadBySlug(ctx, nil, projectID, slug) - if err == nil { - return &asset, nil +func (s *assetService) FindOrCreateAsset(ctx context.Context, rbac shared.AccessControl, providerID string, orgID uuid.UUID, projectID uuid.UUID, name string, currentUser string) (*models.Asset, error) { + providerID = "dn:" + providerID + externalID := name + + asset := &models.Asset{ + Name: name, + Slug: slug.Make(name), + ProjectID: projectID, + ExternalEntityID: &externalID, + ExternalEntityProviderID: &providerID, } - if !errors.Is(err, gorm.ErrRecordNotFound) { + + newAssets, _, err := s.assetRepository.UpsertSplit(ctx, nil, providerID, []*models.Asset{asset}) + if err != nil { return nil, err } - newAsset := models.Asset{ - Name: name, - Slug: slug, - ProjectID: projectID, + if len(newAssets) > 0 { + if err := s.BootstrapAsset(ctx, rbac, asset); err != nil { + return nil, err + } } - return s.CreateAsset(ctx, rbac, currentUser, newAsset) - + return asset, nil } func (s *assetService) CreateAsset(ctx context.Context, rbac shared.AccessControl, currentUser string, asset models.Asset) (*models.Asset, error) { diff --git a/services/project_service.go b/services/project_service.go index 6ae1c96bd..c28e3975b 100644 --- a/services/project_service.go +++ b/services/project_service.go @@ -11,8 +11,6 @@ import ( "github.com/l3montree-dev/devguard/dtos" "github.com/l3montree-dev/devguard/shared" "github.com/labstack/echo/v4" - "github.com/pkg/errors" - "gorm.io/gorm" ) type projectService struct { @@ -39,27 +37,31 @@ func (s *projectService) ReadBySlug(ctx shared.Context, organizationID uuid.UUID return project, nil } -func (s *projectService) FindOrCreateProject(ctx shared.Context, orgID uuid.UUID, name string, parentID uuid.UUID) (*models.Project, error) { - slug := slug.Make(name) - project, err := s.projectRepository.ReadBySlug(ctx.Request().Context(), nil, orgID, slug) - if err == nil { - return &project, nil - } - if !errors.Is(err, gorm.ErrRecordNotFound) { +func (s *projectService) FindOrCreateProject(ctx shared.Context, providerID string, orgID uuid.UUID, name string, parentID uuid.UUID) (*models.Project, error) { + providerID = "dn:" + providerID + externalID := name + project := &models.Project{ + Name: name, + Slug: slug.Make(name), + OrganizationID: orgID, + ParentID: &parentID, + Type: models.ProjectTypeDynamic, + ExternalEntityID: &externalID, + ExternalEntityProviderID: &providerID, + } + newProjects, _, err := s.projectRepository.UpsertSplit(ctx.Request().Context(), nil, providerID, []*models.Project{project}) + if err != nil { return nil, err } - newProject := &models.Project{ - Name: name, - Slug: slug, - OrganizationID: orgID, - ParentID: &parentID, - } - err = s.CreateProject(ctx, newProject) - if err != nil { - return nil, err + if len(newProjects) > 0 { + domainRBAC := shared.GetRBAC(ctx) + if err := s.BootstrapProject(ctx.Request().Context(), domainRBAC, project); err != nil { + return nil, err + } } - return newProject, nil + + return project, nil } func (s *projectService) CreateProject(ctx shared.Context, project *models.Project) error { diff --git a/shared/common_interfaces.go b/shared/common_interfaces.go index 5efdf9eff..c1278be1d 100644 --- a/shared/common_interfaces.go +++ b/shared/common_interfaces.go @@ -158,6 +158,8 @@ type AssetRepository interface { GetAllAssetsFromDB(ctx context.Context, tx DB) ([]models.Asset, error) ReadWithAssetVersions(ctx context.Context, tx DB, assetID uuid.UUID) (models.Asset, error) GetAssetsWithVulnSharingEnabled(ctx context.Context, tx DB, orgID uuid.UUID) ([]models.Asset, error) + Upsert(ctx context.Context, tx DB, assets *[]*models.Asset, conflictingColumns []clause.Column, updateOnly []string) error + UpsertSplit(ctx context.Context, tx DB, externalProviderID string, assets []*models.Asset) ([]*models.Asset, []*models.Asset, error) } type AttestationRepository interface { @@ -376,7 +378,7 @@ type ProjectService interface { CreateProject(ctx Context, project *models.Project) error BootstrapProject(ctx context.Context, rbac AccessControl, project *models.Project) error SearchProjectsWithSubProjectsAndAssetsPaged(c Context) (Paged[dtos.ProjectDTO], error) - FindOrCreateProject(ctx Context, orgID uuid.UUID, name string, parentID uuid.UUID) (*models.Project, error) + FindOrCreateProject(ctx Context, providerID string, orgID uuid.UUID, name string, parentID uuid.UUID) (*models.Project, error) } type InTotoVerifierService interface { @@ -391,7 +393,7 @@ type AssetService interface { GetCVSSBadgeSVG(ctx context.Context, latest *models.ArtifactRiskHistory) string CreateAsset(ctx context.Context, rbac AccessControl, currentUserID string, asset models.Asset) (*models.Asset, error) BootstrapAsset(ctx context.Context, rbac AccessControl, asset *models.Asset) error - FindOrCreateAsset(ctx context.Context, rbac AccessControl, orgID uuid.UUID, projectID uuid.UUID, name string, currentUser string) (*models.Asset, error) + FindOrCreateAsset(ctx context.Context, rbac AccessControl, providerID string, orgID uuid.UUID, projectID uuid.UUID, name string, currentUser string) (*models.Asset, error) } type ArtifactService interface { GetArtifactsByAssetIDAndAssetVersionName(ctx context.Context, tx DB, assetID uuid.UUID, assetVersionName string) ([]models.Artifact, error) diff --git a/transformer/project_transformer.go b/transformer/project_transformer.go index 39aafeb09..2ff4c0072 100644 --- a/transformer/project_transformer.go +++ b/transformer/project_transformer.go @@ -25,10 +25,9 @@ import ( func ProjectCreateRequestToModel(projectCreate dtos.ProjectCreateRequest) models.Project { // check if valid type projectType := projectCreate.Type - if projectType != string(models.ProjectTypeDefault) && projectType != string(models.ProjectTypeKubernetesNamespace) { + if projectType == "" { projectType = string(models.ProjectTypeDefault) } - return models.Project{ Name: projectCreate.Name, Slug: slug.Make(projectCreate.Name), From 6a10f50ac907cf0d354735acdee4523dc6c33703 Mon Sep 17 00:00:00 2001 From: rafi Date: Tue, 16 Jun 2026 13:01:59 +0200 Subject: [PATCH 03/10] add external entity provider middleware for dynamic project routing Signed-off-by: rafi --- controllers/project_controller.go | 9 ++------ .../external_entity_provider_middlewares.go | 21 +++++++++++++++++++ router/project_router.go | 6 ++++-- router/router_test.go | 1 + shared/context_utils.go | 9 ++++++++ 5 files changed, 37 insertions(+), 9 deletions(-) diff --git a/controllers/project_controller.go b/controllers/project_controller.go index e1143b4a7..ab374e3fb 100644 --- a/controllers/project_controller.go +++ b/controllers/project_controller.go @@ -20,7 +20,6 @@ import ( "fmt" "io" "log/slog" - "strings" cdx "github.com/CycloneDX/cyclonedx-go" "github.com/l3montree-dev/devguard/database/models" @@ -542,9 +541,7 @@ func (ProjectController *ProjectController) HandleDynamicProject(ctx shared.Cont projectName := probe.ProjectExternalEntityID assetName := probe.AssetExternalEntityID assetVersionName := probe.AssetVersion - providerID := ctx.Param("providerID") - //delete the trailing slash if it exists - providerID = strings.TrimSuffix(providerID, "/") + providerID := shared.GetProviderID(ctx) organization := shared.GetOrg(ctx) parentProject := shared.GetProject(ctx) userID := shared.GetSession(ctx).GetUserID() @@ -624,9 +621,7 @@ func (ProjectController *ProjectController) ListDynamicProjects(ctx shared.Conte reqCtx := ctx.Request().Context() parentProject := shared.GetProject(ctx) - providerID := ctx.Param("providerID") - //delete the trailing slash if it exists - providerID = strings.TrimSuffix(providerID, "/") + providerID := shared.GetProviderID(ctx) //TOD:: we should optimize this by doing it in a single query. projects, err := ProjectController.projectRepository.GetDirectChildProjects(reqCtx, nil, parentProject.ID) diff --git a/middlewares/external_entity_provider_middlewares.go b/middlewares/external_entity_provider_middlewares.go index 0e12a1b98..95a87bb7f 100644 --- a/middlewares/external_entity_provider_middlewares.go +++ b/middlewares/external_entity_provider_middlewares.go @@ -3,14 +3,35 @@ package middlewares import ( "context" "log/slog" + "strings" "sync" "time" + "github.com/l3montree-dev/devguard/integrations/gitlabint" "github.com/l3montree-dev/devguard/shared" "github.com/labstack/echo/v4" "go.opentelemetry.io/otel" ) +// ProviderIDMiddleware extracts the :providerID URL param, normalizes it, stores it in the context, +// and rejects IDs that collide with a configured GitLab integration. +func ProviderIDMiddleware(gitlabIntegrations map[string]*gitlabint.GitlabOauth2Config) shared.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(ctx shared.Context) error { + providerID := ctx.Param("providerID") + providerID = strings.TrimSuffix(providerID, "/") + if providerID == "" { + return echo.NewHTTPError(400, "providerID is required") + } + if _, isGitLab := gitlabIntegrations[providerID]; isGitLab { + return echo.NewHTTPError(400, "providerID is reserved") + } + shared.SetProviderID(ctx, providerID) + return next(ctx) + } + } +} + // ExternalEntityProviderOrgSyncMiddleware returns a middleware that triggers a background org sync // for external entity providers. It rate-limits per user so the sync runs at most once every 15 minutes. func ExternalEntityProviderOrgSyncMiddleware(externalEntityProviderService shared.ExternalEntityProviderService) shared.MiddlewareFunc { diff --git a/router/project_router.go b/router/project_router.go index d5dba3c52..e34e8ab9a 100644 --- a/router/project_router.go +++ b/router/project_router.go @@ -18,6 +18,7 @@ package router import ( "github.com/l3montree-dev/devguard/controllers" "github.com/l3montree-dev/devguard/controllers/dependencyfirewall" + "github.com/l3montree-dev/devguard/integrations/gitlabint" "github.com/l3montree-dev/devguard/middlewares" "github.com/l3montree-dev/devguard/shared" "github.com/labstack/echo/v4" @@ -39,6 +40,7 @@ func NewProjectRouter( webhookIntegration *controllers.WebhookController, projectRepository shared.ProjectRepository, componentController *controllers.ComponentController, + gitlabIntegrations map[string]*gitlabint.GitlabOauth2Config, ) ProjectRouter { /** Project scoped router @@ -68,8 +70,8 @@ func NewProjectRouter( projectRouter.GET("/releases/", releaseController.List) projectRouter.GET("/components/", componentController.SearchComponentOccurrences, projectScopedRBAC(shared.ObjectAsset, shared.ActionCreate)) - projectRouter.POST("/dynamic-project/dn/:providerID", projectController.HandleDynamicProject, middlewares.NeededScope([]string{"manage"})) - projectRouter.GET("/dynamic-project/dn/:providerID", projectController.ListDynamicProjects, middlewares.NeededScope([]string{"manage"})) + projectRouter.POST("/dynamic-project/dn/:providerID", projectController.HandleDynamicProject, middlewares.ProviderIDMiddleware(gitlabIntegrations), middlewares.NeededScope([]string{"manage"})) + projectRouter.GET("/dynamic-project/dn/:providerID", projectController.ListDynamicProjects, middlewares.ProviderIDMiddleware(gitlabIntegrations), middlewares.NeededScope([]string{"manage"})) projectRouter.POST("/assets/", assetController.Create, middlewares.NeededScope([]string{"manage"}), projectScopedRBAC(shared.ObjectAsset, shared.ActionCreate)) diff --git a/router/router_test.go b/router/router_test.go index 7b7251bba..9ceead37a 100644 --- a/router/router_test.go +++ b/router/router_test.go @@ -241,6 +241,7 @@ func buildSecurityTestServer(t *testing.T, ac *mocks.AccessControl) *echo.Echo { new(controllers.WebhookController), projectRepo, new(controllers.ComponentController), + nil, ) assetRouter := NewAssetRouter( diff --git a/shared/context_utils.go b/shared/context_utils.go index fc4807de5..0fd8ccf0d 100644 --- a/shared/context_utils.go +++ b/shared/context_utils.go @@ -366,6 +366,15 @@ func GetProject(ctx Context) models.Project { return ctx.Get("project").(models.Project) } +func SetProviderID(ctx Context, providerID string) { + ctx.Set("providerID", providerID) +} + +func GetProviderID(ctx Context) string { + v, _ := ctx.Get("providerID").(string) + return v +} + func GetRepositoryID(asset *models.Asset) (string, error) { if asset.RepositoryID != nil { return *asset.RepositoryID, nil From e0fd14b0e13442e7063955700dbbcbfc1e434fcf Mon Sep 17 00:00:00 2001 From: rafi Date: Tue, 16 Jun 2026 13:03:21 +0200 Subject: [PATCH 04/10] fix mocks Signed-off-by: rafi --- mocks/mock_AssetRepository.go | 104 +++++++- mocks/mock_AssetService.go | 99 ++++++++ mocks/mock_ExternalPropertyFileReference.go | 36 --- mocks/mock_ProjectRepository.go | 87 +++++++ mocks/mock_ProjectService.go | 86 +++++++ mocks/mock_ReportingDescriptorReference.go | 36 --- mocks/mock_VulnDBImportService.go | 260 -------------------- 7 files changed, 368 insertions(+), 340 deletions(-) delete mode 100644 mocks/mock_ExternalPropertyFileReference.go delete mode 100644 mocks/mock_ReportingDescriptorReference.go delete mode 100644 mocks/mock_VulnDBImportService.go diff --git a/mocks/mock_AssetRepository.go b/mocks/mock_AssetRepository.go index c3954e3c4..27036fe34 100644 --- a/mocks/mock_AssetRepository.go +++ b/mocks/mock_AssetRepository.go @@ -1945,8 +1945,8 @@ func (_c *AssetRepository_Update_Call) RunAndReturn(run func(ctx context.Context } // Upsert provides a mock function for the type AssetRepository -func (_mock *AssetRepository) Upsert(ctx context.Context, tx shared.DB, t *[]*models.Asset, conflictingColumns []clause.Column, updateOnly []string) error { - ret := _mock.Called(ctx, tx, t, conflictingColumns, updateOnly) +func (_mock *AssetRepository) Upsert(ctx context.Context, tx shared.DB, assets *[]*models.Asset, conflictingColumns []clause.Column, updateOnly []string) error { + ret := _mock.Called(ctx, tx, assets, conflictingColumns, updateOnly) if len(ret) == 0 { panic("no return value specified for Upsert") @@ -1954,7 +1954,7 @@ func (_mock *AssetRepository) Upsert(ctx context.Context, tx shared.DB, t *[]*mo var r0 error if returnFunc, ok := ret.Get(0).(func(context.Context, shared.DB, *[]*models.Asset, []clause.Column, []string) error); ok { - r0 = returnFunc(ctx, tx, t, conflictingColumns, updateOnly) + r0 = returnFunc(ctx, tx, assets, conflictingColumns, updateOnly) } else { r0 = ret.Error(0) } @@ -1969,14 +1969,14 @@ type AssetRepository_Upsert_Call struct { // Upsert is a helper method to define mock.On call // - ctx context.Context // - tx shared.DB -// - t *[]*models.Asset +// - assets *[]*models.Asset // - conflictingColumns []clause.Column // - updateOnly []string -func (_e *AssetRepository_Expecter) Upsert(ctx interface{}, tx interface{}, t interface{}, conflictingColumns interface{}, updateOnly interface{}) *AssetRepository_Upsert_Call { - return &AssetRepository_Upsert_Call{Call: _e.mock.On("Upsert", ctx, tx, t, conflictingColumns, updateOnly)} +func (_e *AssetRepository_Expecter) Upsert(ctx interface{}, tx interface{}, assets interface{}, conflictingColumns interface{}, updateOnly interface{}) *AssetRepository_Upsert_Call { + return &AssetRepository_Upsert_Call{Call: _e.mock.On("Upsert", ctx, tx, assets, conflictingColumns, updateOnly)} } -func (_c *AssetRepository_Upsert_Call) Run(run func(ctx context.Context, tx shared.DB, t *[]*models.Asset, conflictingColumns []clause.Column, updateOnly []string)) *AssetRepository_Upsert_Call { +func (_c *AssetRepository_Upsert_Call) Run(run func(ctx context.Context, tx shared.DB, assets *[]*models.Asset, conflictingColumns []clause.Column, updateOnly []string)) *AssetRepository_Upsert_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -2014,7 +2014,95 @@ func (_c *AssetRepository_Upsert_Call) Return(err error) *AssetRepository_Upsert return _c } -func (_c *AssetRepository_Upsert_Call) RunAndReturn(run func(ctx context.Context, tx shared.DB, t *[]*models.Asset, conflictingColumns []clause.Column, updateOnly []string) error) *AssetRepository_Upsert_Call { +func (_c *AssetRepository_Upsert_Call) RunAndReturn(run func(ctx context.Context, tx shared.DB, assets *[]*models.Asset, conflictingColumns []clause.Column, updateOnly []string) error) *AssetRepository_Upsert_Call { + _c.Call.Return(run) + return _c +} + +// UpsertSplit provides a mock function for the type AssetRepository +func (_mock *AssetRepository) UpsertSplit(ctx context.Context, tx shared.DB, externalProviderID string, assets []*models.Asset) ([]*models.Asset, []*models.Asset, error) { + ret := _mock.Called(ctx, tx, externalProviderID, assets) + + if len(ret) == 0 { + panic("no return value specified for UpsertSplit") + } + + var r0 []*models.Asset + var r1 []*models.Asset + var r2 error + if returnFunc, ok := ret.Get(0).(func(context.Context, shared.DB, string, []*models.Asset) ([]*models.Asset, []*models.Asset, error)); ok { + return returnFunc(ctx, tx, externalProviderID, assets) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, shared.DB, string, []*models.Asset) []*models.Asset); ok { + r0 = returnFunc(ctx, tx, externalProviderID, assets) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.Asset) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, shared.DB, string, []*models.Asset) []*models.Asset); ok { + r1 = returnFunc(ctx, tx, externalProviderID, assets) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).([]*models.Asset) + } + } + if returnFunc, ok := ret.Get(2).(func(context.Context, shared.DB, string, []*models.Asset) error); ok { + r2 = returnFunc(ctx, tx, externalProviderID, assets) + } else { + r2 = ret.Error(2) + } + return r0, r1, r2 +} + +// AssetRepository_UpsertSplit_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpsertSplit' +type AssetRepository_UpsertSplit_Call struct { + *mock.Call +} + +// UpsertSplit is a helper method to define mock.On call +// - ctx context.Context +// - tx shared.DB +// - externalProviderID string +// - assets []*models.Asset +func (_e *AssetRepository_Expecter) UpsertSplit(ctx interface{}, tx interface{}, externalProviderID interface{}, assets interface{}) *AssetRepository_UpsertSplit_Call { + return &AssetRepository_UpsertSplit_Call{Call: _e.mock.On("UpsertSplit", ctx, tx, externalProviderID, assets)} +} + +func (_c *AssetRepository_UpsertSplit_Call) Run(run func(ctx context.Context, tx shared.DB, externalProviderID string, assets []*models.Asset)) *AssetRepository_UpsertSplit_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 shared.DB + if args[1] != nil { + arg1 = args[1].(shared.DB) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + var arg3 []*models.Asset + if args[3] != nil { + arg3 = args[3].([]*models.Asset) + } + run( + arg0, + arg1, + arg2, + arg3, + ) + }) + return _c +} + +func (_c *AssetRepository_UpsertSplit_Call) Return(assets1 []*models.Asset, assets2 []*models.Asset, err error) *AssetRepository_UpsertSplit_Call { + _c.Call.Return(assets1, assets2, err) + return _c +} + +func (_c *AssetRepository_UpsertSplit_Call) RunAndReturn(run func(ctx context.Context, tx shared.DB, externalProviderID string, assets []*models.Asset) ([]*models.Asset, []*models.Asset, error)) *AssetRepository_UpsertSplit_Call { _c.Call.Return(run) return _c } diff --git a/mocks/mock_AssetService.go b/mocks/mock_AssetService.go index c8fcb3484..b5ff0663f 100644 --- a/mocks/mock_AssetService.go +++ b/mocks/mock_AssetService.go @@ -7,6 +7,7 @@ package mocks import ( "context" + "github.com/google/uuid" "github.com/l3montree-dev/devguard/database/models" "github.com/l3montree-dev/devguard/shared" mock "github.com/stretchr/testify/mock" @@ -182,6 +183,104 @@ func (_c *AssetService_CreateAsset_Call) RunAndReturn(run func(ctx context.Conte return _c } +// FindOrCreateAsset provides a mock function for the type AssetService +func (_mock *AssetService) FindOrCreateAsset(ctx context.Context, rbac shared.AccessControl, providerID string, orgID uuid.UUID, projectID uuid.UUID, name string, currentUser string) (*models.Asset, error) { + ret := _mock.Called(ctx, rbac, providerID, orgID, projectID, name, currentUser) + + if len(ret) == 0 { + panic("no return value specified for FindOrCreateAsset") + } + + var r0 *models.Asset + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, shared.AccessControl, string, uuid.UUID, uuid.UUID, string, string) (*models.Asset, error)); ok { + return returnFunc(ctx, rbac, providerID, orgID, projectID, name, currentUser) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, shared.AccessControl, string, uuid.UUID, uuid.UUID, string, string) *models.Asset); ok { + r0 = returnFunc(ctx, rbac, providerID, orgID, projectID, name, currentUser) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Asset) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, shared.AccessControl, string, uuid.UUID, uuid.UUID, string, string) error); ok { + r1 = returnFunc(ctx, rbac, providerID, orgID, projectID, name, currentUser) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// AssetService_FindOrCreateAsset_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindOrCreateAsset' +type AssetService_FindOrCreateAsset_Call struct { + *mock.Call +} + +// FindOrCreateAsset is a helper method to define mock.On call +// - ctx context.Context +// - rbac shared.AccessControl +// - providerID string +// - orgID uuid.UUID +// - projectID uuid.UUID +// - name string +// - currentUser string +func (_e *AssetService_Expecter) FindOrCreateAsset(ctx interface{}, rbac interface{}, providerID interface{}, orgID interface{}, projectID interface{}, name interface{}, currentUser interface{}) *AssetService_FindOrCreateAsset_Call { + return &AssetService_FindOrCreateAsset_Call{Call: _e.mock.On("FindOrCreateAsset", ctx, rbac, providerID, orgID, projectID, name, currentUser)} +} + +func (_c *AssetService_FindOrCreateAsset_Call) Run(run func(ctx context.Context, rbac shared.AccessControl, providerID string, orgID uuid.UUID, projectID uuid.UUID, name string, currentUser string)) *AssetService_FindOrCreateAsset_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 shared.AccessControl + if args[1] != nil { + arg1 = args[1].(shared.AccessControl) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + var arg3 uuid.UUID + if args[3] != nil { + arg3 = args[3].(uuid.UUID) + } + var arg4 uuid.UUID + if args[4] != nil { + arg4 = args[4].(uuid.UUID) + } + var arg5 string + if args[5] != nil { + arg5 = args[5].(string) + } + var arg6 string + if args[6] != nil { + arg6 = args[6].(string) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + arg5, + arg6, + ) + }) + return _c +} + +func (_c *AssetService_FindOrCreateAsset_Call) Return(asset *models.Asset, err error) *AssetService_FindOrCreateAsset_Call { + _c.Call.Return(asset, err) + return _c +} + +func (_c *AssetService_FindOrCreateAsset_Call) RunAndReturn(run func(ctx context.Context, rbac shared.AccessControl, providerID string, orgID uuid.UUID, projectID uuid.UUID, name string, currentUser string) (*models.Asset, error)) *AssetService_FindOrCreateAsset_Call { + _c.Call.Return(run) + return _c +} + // GetCVSSBadgeSVG provides a mock function for the type AssetService func (_mock *AssetService) GetCVSSBadgeSVG(ctx context.Context, latest *models.ArtifactRiskHistory) string { ret := _mock.Called(ctx, latest) diff --git a/mocks/mock_ExternalPropertyFileReference.go b/mocks/mock_ExternalPropertyFileReference.go deleted file mode 100644 index a2163049d..000000000 --- a/mocks/mock_ExternalPropertyFileReference.go +++ /dev/null @@ -1,36 +0,0 @@ -// Code generated by mockery; DO NOT EDIT. -// github.com/vektra/mockery -// template: testify - -package mocks - -import ( - mock "github.com/stretchr/testify/mock" -) - -// NewExternalPropertyFileReference creates a new instance of ExternalPropertyFileReference. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewExternalPropertyFileReference(t interface { - mock.TestingT - Cleanup(func()) -}) *ExternalPropertyFileReference { - mock := &ExternalPropertyFileReference{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} - -// ExternalPropertyFileReference is an autogenerated mock type for the ExternalPropertyFileReference type -type ExternalPropertyFileReference struct { - mock.Mock -} - -type ExternalPropertyFileReference_Expecter struct { - mock *mock.Mock -} - -func (_m *ExternalPropertyFileReference) EXPECT() *ExternalPropertyFileReference_Expecter { - return &ExternalPropertyFileReference_Expecter{mock: &_m.Mock} -} diff --git a/mocks/mock_ProjectRepository.go b/mocks/mock_ProjectRepository.go index 73ccd679f..91c5c9b8f 100644 --- a/mocks/mock_ProjectRepository.go +++ b/mocks/mock_ProjectRepository.go @@ -173,6 +173,93 @@ func (_c *ProjectRepository_All_Call) RunAndReturn(run func(ctx context.Context, return _c } +// CleanupDynamicProject provides a mock function for the type ProjectRepository +func (_mock *ProjectRepository) CleanupDynamicProject(ctx context.Context, tx shared.DB, organizationID uuid.UUID, parentProjectID uuid.UUID, projectName string, assetName string, assetVersionName string) error { + ret := _mock.Called(ctx, tx, organizationID, parentProjectID, projectName, assetName, assetVersionName) + + if len(ret) == 0 { + panic("no return value specified for CleanupDynamicProject") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, shared.DB, uuid.UUID, uuid.UUID, string, string, string) error); ok { + r0 = returnFunc(ctx, tx, organizationID, parentProjectID, projectName, assetName, assetVersionName) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// ProjectRepository_CleanupDynamicProject_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CleanupDynamicProject' +type ProjectRepository_CleanupDynamicProject_Call struct { + *mock.Call +} + +// CleanupDynamicProject is a helper method to define mock.On call +// - ctx context.Context +// - tx shared.DB +// - organizationID uuid.UUID +// - parentProjectID uuid.UUID +// - projectName string +// - assetName string +// - assetVersionName string +func (_e *ProjectRepository_Expecter) CleanupDynamicProject(ctx interface{}, tx interface{}, organizationID interface{}, parentProjectID interface{}, projectName interface{}, assetName interface{}, assetVersionName interface{}) *ProjectRepository_CleanupDynamicProject_Call { + return &ProjectRepository_CleanupDynamicProject_Call{Call: _e.mock.On("CleanupDynamicProject", ctx, tx, organizationID, parentProjectID, projectName, assetName, assetVersionName)} +} + +func (_c *ProjectRepository_CleanupDynamicProject_Call) Run(run func(ctx context.Context, tx shared.DB, organizationID uuid.UUID, parentProjectID uuid.UUID, projectName string, assetName string, assetVersionName string)) *ProjectRepository_CleanupDynamicProject_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 shared.DB + if args[1] != nil { + arg1 = args[1].(shared.DB) + } + var arg2 uuid.UUID + if args[2] != nil { + arg2 = args[2].(uuid.UUID) + } + var arg3 uuid.UUID + if args[3] != nil { + arg3 = args[3].(uuid.UUID) + } + var arg4 string + if args[4] != nil { + arg4 = args[4].(string) + } + var arg5 string + if args[5] != nil { + arg5 = args[5].(string) + } + var arg6 string + if args[6] != nil { + arg6 = args[6].(string) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + arg5, + arg6, + ) + }) + return _c +} + +func (_c *ProjectRepository_CleanupDynamicProject_Call) Return(err error) *ProjectRepository_CleanupDynamicProject_Call { + _c.Call.Return(err) + return _c +} + +func (_c *ProjectRepository_CleanupDynamicProject_Call) RunAndReturn(run func(ctx context.Context, tx shared.DB, organizationID uuid.UUID, parentProjectID uuid.UUID, projectName string, assetName string, assetVersionName string) error) *ProjectRepository_CleanupDynamicProject_Call { + _c.Call.Return(run) + return _c +} + // Create provides a mock function for the type ProjectRepository func (_mock *ProjectRepository) Create(ctx context.Context, tx shared.DB, project *models.Project) error { ret := _mock.Called(ctx, tx, project) diff --git a/mocks/mock_ProjectService.go b/mocks/mock_ProjectService.go index 85256a5d0..15dd6b62c 100644 --- a/mocks/mock_ProjectService.go +++ b/mocks/mock_ProjectService.go @@ -161,6 +161,92 @@ func (_c *ProjectService_CreateProject_Call) RunAndReturn(run func(ctx shared.Co return _c } +// FindOrCreateProject provides a mock function for the type ProjectService +func (_mock *ProjectService) FindOrCreateProject(ctx shared.Context, providerID string, orgID uuid.UUID, name string, parentID uuid.UUID) (*models.Project, error) { + ret := _mock.Called(ctx, providerID, orgID, name, parentID) + + if len(ret) == 0 { + panic("no return value specified for FindOrCreateProject") + } + + var r0 *models.Project + var r1 error + if returnFunc, ok := ret.Get(0).(func(shared.Context, string, uuid.UUID, string, uuid.UUID) (*models.Project, error)); ok { + return returnFunc(ctx, providerID, orgID, name, parentID) + } + if returnFunc, ok := ret.Get(0).(func(shared.Context, string, uuid.UUID, string, uuid.UUID) *models.Project); ok { + r0 = returnFunc(ctx, providerID, orgID, name, parentID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Project) + } + } + if returnFunc, ok := ret.Get(1).(func(shared.Context, string, uuid.UUID, string, uuid.UUID) error); ok { + r1 = returnFunc(ctx, providerID, orgID, name, parentID) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// ProjectService_FindOrCreateProject_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindOrCreateProject' +type ProjectService_FindOrCreateProject_Call struct { + *mock.Call +} + +// FindOrCreateProject is a helper method to define mock.On call +// - ctx shared.Context +// - providerID string +// - orgID uuid.UUID +// - name string +// - parentID uuid.UUID +func (_e *ProjectService_Expecter) FindOrCreateProject(ctx interface{}, providerID interface{}, orgID interface{}, name interface{}, parentID interface{}) *ProjectService_FindOrCreateProject_Call { + return &ProjectService_FindOrCreateProject_Call{Call: _e.mock.On("FindOrCreateProject", ctx, providerID, orgID, name, parentID)} +} + +func (_c *ProjectService_FindOrCreateProject_Call) Run(run func(ctx shared.Context, providerID string, orgID uuid.UUID, name string, parentID uuid.UUID)) *ProjectService_FindOrCreateProject_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 shared.Context + if args[0] != nil { + arg0 = args[0].(shared.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 uuid.UUID + if args[2] != nil { + arg2 = args[2].(uuid.UUID) + } + var arg3 string + if args[3] != nil { + arg3 = args[3].(string) + } + var arg4 uuid.UUID + if args[4] != nil { + arg4 = args[4].(uuid.UUID) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + ) + }) + return _c +} + +func (_c *ProjectService_FindOrCreateProject_Call) Return(project *models.Project, err error) *ProjectService_FindOrCreateProject_Call { + _c.Call.Return(project, err) + return _c +} + +func (_c *ProjectService_FindOrCreateProject_Call) RunAndReturn(run func(ctx shared.Context, providerID string, orgID uuid.UUID, name string, parentID uuid.UUID) (*models.Project, error)) *ProjectService_FindOrCreateProject_Call { + _c.Call.Return(run) + return _c +} + // GetDirectChildProjects provides a mock function for the type ProjectService func (_mock *ProjectService) GetDirectChildProjects(ctx context.Context, projectID uuid.UUID) ([]models.Project, error) { ret := _mock.Called(ctx, projectID) diff --git a/mocks/mock_ReportingDescriptorReference.go b/mocks/mock_ReportingDescriptorReference.go deleted file mode 100644 index e38e0efb6..000000000 --- a/mocks/mock_ReportingDescriptorReference.go +++ /dev/null @@ -1,36 +0,0 @@ -// Code generated by mockery; DO NOT EDIT. -// github.com/vektra/mockery -// template: testify - -package mocks - -import ( - mock "github.com/stretchr/testify/mock" -) - -// NewReportingDescriptorReference creates a new instance of ReportingDescriptorReference. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewReportingDescriptorReference(t interface { - mock.TestingT - Cleanup(func()) -}) *ReportingDescriptorReference { - mock := &ReportingDescriptorReference{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} - -// ReportingDescriptorReference is an autogenerated mock type for the ReportingDescriptorReference type -type ReportingDescriptorReference struct { - mock.Mock -} - -type ReportingDescriptorReference_Expecter struct { - mock *mock.Mock -} - -func (_m *ReportingDescriptorReference) EXPECT() *ReportingDescriptorReference_Expecter { - return &ReportingDescriptorReference_Expecter{mock: &_m.Mock} -} diff --git a/mocks/mock_VulnDBImportService.go b/mocks/mock_VulnDBImportService.go deleted file mode 100644 index fbc5bbcf9..000000000 --- a/mocks/mock_VulnDBImportService.go +++ /dev/null @@ -1,260 +0,0 @@ -// Code generated by mockery; DO NOT EDIT. -// github.com/vektra/mockery -// template: testify - -package mocks - -import ( - "context" - - mock "github.com/stretchr/testify/mock" -) - -// NewVulnDBImportService creates a new instance of VulnDBImportService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewVulnDBImportService(t interface { - mock.TestingT - Cleanup(func()) -}) *VulnDBImportService { - mock := &VulnDBImportService{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} - -// VulnDBImportService is an autogenerated mock type for the VulnDBImportService type -type VulnDBImportService struct { - mock.Mock -} - -type VulnDBImportService_Expecter struct { - mock *mock.Mock -} - -func (_m *VulnDBImportService) EXPECT() *VulnDBImportService_Expecter { - return &VulnDBImportService_Expecter{mock: &_m.Mock} -} - -// CleanupOrphanedTables provides a mock function for the type VulnDBImportService -func (_mock *VulnDBImportService) CleanupOrphanedTables(ctx context.Context) error { - ret := _mock.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for CleanupOrphanedTables") - } - - var r0 error - if returnFunc, ok := ret.Get(0).(func(context.Context) error); ok { - r0 = returnFunc(ctx) - } else { - r0 = ret.Error(0) - } - return r0 -} - -// VulnDBImportService_CleanupOrphanedTables_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CleanupOrphanedTables' -type VulnDBImportService_CleanupOrphanedTables_Call struct { - *mock.Call -} - -// CleanupOrphanedTables is a helper method to define mock.On call -// - ctx context.Context -func (_e *VulnDBImportService_Expecter) CleanupOrphanedTables(ctx interface{}) *VulnDBImportService_CleanupOrphanedTables_Call { - return &VulnDBImportService_CleanupOrphanedTables_Call{Call: _e.mock.On("CleanupOrphanedTables", ctx)} -} - -func (_c *VulnDBImportService_CleanupOrphanedTables_Call) Run(run func(ctx context.Context)) *VulnDBImportService_CleanupOrphanedTables_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - run( - arg0, - ) - }) - return _c -} - -func (_c *VulnDBImportService_CleanupOrphanedTables_Call) Return(err error) *VulnDBImportService_CleanupOrphanedTables_Call { - _c.Call.Return(err) - return _c -} - -func (_c *VulnDBImportService_CleanupOrphanedTables_Call) RunAndReturn(run func(ctx context.Context) error) *VulnDBImportService_CleanupOrphanedTables_Call { - _c.Call.Return(run) - return _c -} - -// CreateTablesWithSuffix provides a mock function for the type VulnDBImportService -func (_mock *VulnDBImportService) CreateTablesWithSuffix(ctx context.Context, suffix string) error { - ret := _mock.Called(ctx, suffix) - - if len(ret) == 0 { - panic("no return value specified for CreateTablesWithSuffix") - } - - var r0 error - if returnFunc, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = returnFunc(ctx, suffix) - } else { - r0 = ret.Error(0) - } - return r0 -} - -// VulnDBImportService_CreateTablesWithSuffix_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateTablesWithSuffix' -type VulnDBImportService_CreateTablesWithSuffix_Call struct { - *mock.Call -} - -// CreateTablesWithSuffix is a helper method to define mock.On call -// - ctx context.Context -// - suffix string -func (_e *VulnDBImportService_Expecter) CreateTablesWithSuffix(ctx interface{}, suffix interface{}) *VulnDBImportService_CreateTablesWithSuffix_Call { - return &VulnDBImportService_CreateTablesWithSuffix_Call{Call: _e.mock.On("CreateTablesWithSuffix", ctx, suffix)} -} - -func (_c *VulnDBImportService_CreateTablesWithSuffix_Call) Run(run func(ctx context.Context, suffix string)) *VulnDBImportService_CreateTablesWithSuffix_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - run( - arg0, - arg1, - ) - }) - return _c -} - -func (_c *VulnDBImportService_CreateTablesWithSuffix_Call) Return(err error) *VulnDBImportService_CreateTablesWithSuffix_Call { - _c.Call.Return(err) - return _c -} - -func (_c *VulnDBImportService_CreateTablesWithSuffix_Call) RunAndReturn(run func(ctx context.Context, suffix string) error) *VulnDBImportService_CreateTablesWithSuffix_Call { - _c.Call.Return(run) - return _c -} - -// ExportDiffs provides a mock function for the type VulnDBImportService -func (_mock *VulnDBImportService) ExportDiffs(ctx context.Context, extraTableNameSuffix string) error { - ret := _mock.Called(ctx, extraTableNameSuffix) - - if len(ret) == 0 { - panic("no return value specified for ExportDiffs") - } - - var r0 error - if returnFunc, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = returnFunc(ctx, extraTableNameSuffix) - } else { - r0 = ret.Error(0) - } - return r0 -} - -// VulnDBImportService_ExportDiffs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ExportDiffs' -type VulnDBImportService_ExportDiffs_Call struct { - *mock.Call -} - -// ExportDiffs is a helper method to define mock.On call -// - ctx context.Context -// - extraTableNameSuffix string -func (_e *VulnDBImportService_Expecter) ExportDiffs(ctx interface{}, extraTableNameSuffix interface{}) *VulnDBImportService_ExportDiffs_Call { - return &VulnDBImportService_ExportDiffs_Call{Call: _e.mock.On("ExportDiffs", ctx, extraTableNameSuffix)} -} - -func (_c *VulnDBImportService_ExportDiffs_Call) Run(run func(ctx context.Context, extraTableNameSuffix string)) *VulnDBImportService_ExportDiffs_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - run( - arg0, - arg1, - ) - }) - return _c -} - -func (_c *VulnDBImportService_ExportDiffs_Call) Return(err error) *VulnDBImportService_ExportDiffs_Call { - _c.Call.Return(err) - return _c -} - -func (_c *VulnDBImportService_ExportDiffs_Call) RunAndReturn(run func(ctx context.Context, extraTableNameSuffix string) error) *VulnDBImportService_ExportDiffs_Call { - _c.Call.Return(run) - return _c -} - -// ImportFromDiff provides a mock function for the type VulnDBImportService -func (_mock *VulnDBImportService) ImportFromDiff(ctx context.Context, extraTableNameSuffix *string) error { - ret := _mock.Called(ctx, extraTableNameSuffix) - - if len(ret) == 0 { - panic("no return value specified for ImportFromDiff") - } - - var r0 error - if returnFunc, ok := ret.Get(0).(func(context.Context, *string) error); ok { - r0 = returnFunc(ctx, extraTableNameSuffix) - } else { - r0 = ret.Error(0) - } - return r0 -} - -// VulnDBImportService_ImportFromDiff_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ImportFromDiff' -type VulnDBImportService_ImportFromDiff_Call struct { - *mock.Call -} - -// ImportFromDiff is a helper method to define mock.On call -// - ctx context.Context -// - extraTableNameSuffix *string -func (_e *VulnDBImportService_Expecter) ImportFromDiff(ctx interface{}, extraTableNameSuffix interface{}) *VulnDBImportService_ImportFromDiff_Call { - return &VulnDBImportService_ImportFromDiff_Call{Call: _e.mock.On("ImportFromDiff", ctx, extraTableNameSuffix)} -} - -func (_c *VulnDBImportService_ImportFromDiff_Call) Run(run func(ctx context.Context, extraTableNameSuffix *string)) *VulnDBImportService_ImportFromDiff_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 *string - if args[1] != nil { - arg1 = args[1].(*string) - } - run( - arg0, - arg1, - ) - }) - return _c -} - -func (_c *VulnDBImportService_ImportFromDiff_Call) Return(err error) *VulnDBImportService_ImportFromDiff_Call { - _c.Call.Return(err) - return _c -} - -func (_c *VulnDBImportService_ImportFromDiff_Call) RunAndReturn(run func(ctx context.Context, extraTableNameSuffix *string) error) *VulnDBImportService_ImportFromDiff_Call { - _c.Call.Return(run) - return _c -} From cfc7437711bf2d129b253347d11dcfca1a645fef Mon Sep 17 00:00:00 2001 From: rafi Date: Tue, 16 Jun 2026 14:01:25 +0200 Subject: [PATCH 05/10] separate external entity IDs from display names in dynamic project routing Signed-off-by: rafi --- controllers/project_controller.go | 17 ++++++++++------- database/repositories/project_repository.go | 13 +++++++------ dtos/project_dto.go | 4 ++++ .../external_entity_provider_middlewares.go | 1 + services/asset_service.go | 6 ++---- services/project_service.go | 7 +++---- shared/common_interfaces.go | 6 +++--- 7 files changed, 30 insertions(+), 24 deletions(-) diff --git a/controllers/project_controller.go b/controllers/project_controller.go index ab374e3fb..7cbe263f2 100644 --- a/controllers/project_controller.go +++ b/controllers/project_controller.go @@ -538,8 +538,10 @@ func (ProjectController *ProjectController) HandleDynamicProject(ctx shared.Cont } action := probe.Verb - projectName := probe.ProjectExternalEntityID - assetName := probe.AssetExternalEntityID + projectName := probe.ProjectName + projectExternalEntityID := probe.ProjectExternalEntityID + assetName := probe.AssetName + assetExternalEntityID := probe.AssetExternalEntityID assetVersionName := probe.AssetVersion providerID := shared.GetProviderID(ctx) organization := shared.GetOrg(ctx) @@ -547,7 +549,7 @@ func (ProjectController *ProjectController) HandleDynamicProject(ctx shared.Cont userID := shared.GetSession(ctx).GetUserID() if action == "delete" { - err := ProjectController.projectRepository.CleanupDynamicProject(ctx.Request().Context(), nil, organization.GetID(), parentProject.ID, projectName, assetName, assetVersionName) + err := ProjectController.projectRepository.CleanupDynamicProject(ctx.Request().Context(), nil, organization.GetID(), parentProject.ID, providerID, projectExternalEntityID, assetExternalEntityID, assetVersionName) if err != nil { return echo.NewHTTPError(500, fmt.Sprintf("could not delete project: %s", err.Error())).WithInternal(err) } @@ -566,10 +568,10 @@ func (ProjectController *ProjectController) HandleDynamicProject(ctx shared.Cont return echo.NewHTTPError(400, fmt.Sprintf("could not parse CycloneDX BOM: %s", err.Error())).WithInternal(err) } - project, err := ProjectController.projectService.FindOrCreateProject(ctx, providerID, organization.GetID(), projectName, parentProject.ID) + project, err := ProjectController.projectService.FindOrCreateProject(ctx, providerID, organization.GetID(), projectName, projectExternalEntityID, parentProject.ID) rbac := shared.GetRBAC(ctx) - asset, err := ProjectController.assetService.FindOrCreateAsset(ctx.Request().Context(), rbac, providerID, organization.GetID(), project.ID, assetName, userID) + asset, err := ProjectController.assetService.FindOrCreateAsset(ctx.Request().Context(), rbac, providerID, organization.GetID(), project.ID, assetName, assetExternalEntityID, userID) if err != nil { return echo.NewHTTPError(500, fmt.Sprintf("could not create asset: %s", err.Error())).WithInternal(err) } @@ -639,7 +641,7 @@ func (ProjectController *ProjectController) ListDynamicProjects(ctx shared.Conte return echo.NewHTTPError(500, "could not list assets").WithInternal(err) } - entry := dtos.ProjectsAssetAssetVersionsDTO{ProjectExternalEntityID: project.Name} + entry := dtos.ProjectsAssetAssetVersionsDTO{ProjectExternalEntityID: *project.ExternalEntityID, ProjectName: project.Name} for _, asset := range assets { if asset.ExternalEntityProviderID == nil || *asset.ExternalEntityProviderID != providerID { continue @@ -651,8 +653,9 @@ func (ProjectController *ProjectController) ListDynamicProjects(ctx shared.Conte assetEntry := struct { AssetExternalEntityID string `json:"assetExternalEntityId"` + AssetName string `json:"assetName"` Versions []string `json:"versions"` - }{AssetExternalEntityID: asset.Name} + }{AssetExternalEntityID: *asset.ExternalEntityID, AssetName: asset.Name} for _, v := range versions { assetEntry.Versions = append(assetEntry.Versions, v.Name) diff --git a/database/repositories/project_repository.go b/database/repositories/project_repository.go index 7a99d175b..c72c7150c 100644 --- a/database/repositories/project_repository.go +++ b/database/repositories/project_repository.go @@ -592,7 +592,7 @@ func (g *projectRepository) Upsert(ctx context.Context, tx *gorm.DB, t *[]*model return g.GetDB(ctx, tx).Clauses(clause.OnConflict{UpdateAll: true, Columns: conflictingColumns}).Create(t).Error } -func (g *projectRepository) CleanupDynamicProject(ctx context.Context, tx *gorm.DB, organizationID uuid.UUID, parentProjectID uuid.UUID, projectName string, assetName string, assetVersionName string) error { +func (g *projectRepository) CleanupDynamicProject(ctx context.Context, tx *gorm.DB, organizationID uuid.UUID, parentProjectID uuid.UUID, providerID string, projectExternalEntityID string, assetExternalEntityID string, assetVersionName string) error { query := ` WITH target AS ( @@ -601,11 +601,12 @@ WITH a.id AS asset_id, av.name AS asset_version_name FROM projects p - JOIN assets a ON a.project_id = p.id AND a.name = ? + JOIN assets a ON a.project_id = p.id AND a.external_entity_id = ? JOIN asset_versions av ON av.asset_id = a.id AND av.name = ? - WHERE p.organization_id = ? + WHERE p.organization_id = ? AND p.parent_id = ? - AND p.name = ? + AND p.external_entity_provider_id = ? + AND p.external_entity_id = ? AND p.type = 'dynamic' LIMIT 1 ), @@ -635,7 +636,7 @@ WITH SELECT 1` return g.GetDB(ctx, tx).Exec(query, - assetName, assetVersionName, - organizationID, parentProjectID, projectName, + assetExternalEntityID, assetVersionName, + organizationID, parentProjectID, providerID, projectExternalEntityID, ).Error } diff --git a/dtos/project_dto.go b/dtos/project_dto.go index 3f8e2e9bb..e8a6341d2 100644 --- a/dtos/project_dto.go +++ b/dtos/project_dto.go @@ -98,15 +98,19 @@ type ProjectAssetDTO struct { type ProjectsAssetAssetVersionsDTO struct { ProjectExternalEntityID string `json:"projectExternalEntityId"` + ProjectName string `json:"projectName"` Assets []struct { AssetExternalEntityID string `json:"assetExternalEntityId"` + AssetName string `json:"assetName"` Versions []string `json:"versions"` } `json:"assets"` } type DynamicProjectRequestDTO struct { Verb string `json:"verb"` ProjectExternalEntityID string `json:"projectExternalEntityId"` + ProjectName string `json:"projectName"` AssetExternalEntityID string `json:"assetExternalEntityId"` + AssetName string `json:"assetName"` AssetVersion string `json:"assetVersion"` Sbom json.RawMessage `json:"sbom,omitempty"` } diff --git a/middlewares/external_entity_provider_middlewares.go b/middlewares/external_entity_provider_middlewares.go index 95a87bb7f..b4faab2ca 100644 --- a/middlewares/external_entity_provider_middlewares.go +++ b/middlewares/external_entity_provider_middlewares.go @@ -20,6 +20,7 @@ func ProviderIDMiddleware(gitlabIntegrations map[string]*gitlabint.GitlabOauth2C return func(ctx shared.Context) error { providerID := ctx.Param("providerID") providerID = strings.TrimSuffix(providerID, "/") + providerID = "dn:" + providerID // prefix to avoid collisions with other provider IDs in the future if providerID == "" { return echo.NewHTTPError(400, "providerID is required") } diff --git a/services/asset_service.go b/services/asset_service.go index 57b4ef970..5a815ba56 100644 --- a/services/asset_service.go +++ b/services/asset_service.go @@ -46,15 +46,13 @@ func NewAssetService(assetRepository shared.AssetRepository, dependencyVulnRepos } } -func (s *assetService) FindOrCreateAsset(ctx context.Context, rbac shared.AccessControl, providerID string, orgID uuid.UUID, projectID uuid.UUID, name string, currentUser string) (*models.Asset, error) { - providerID = "dn:" + providerID - externalID := name +func (s *assetService) FindOrCreateAsset(ctx context.Context, rbac shared.AccessControl, providerID string, orgID uuid.UUID, projectID uuid.UUID, name string, externalEntityID string, currentUser string) (*models.Asset, error) { asset := &models.Asset{ Name: name, Slug: slug.Make(name), ProjectID: projectID, - ExternalEntityID: &externalID, + ExternalEntityID: &externalEntityID, ExternalEntityProviderID: &providerID, } diff --git a/services/project_service.go b/services/project_service.go index c28e3975b..f51a72c91 100644 --- a/services/project_service.go +++ b/services/project_service.go @@ -37,16 +37,15 @@ func (s *projectService) ReadBySlug(ctx shared.Context, organizationID uuid.UUID return project, nil } -func (s *projectService) FindOrCreateProject(ctx shared.Context, providerID string, orgID uuid.UUID, name string, parentID uuid.UUID) (*models.Project, error) { - providerID = "dn:" + providerID - externalID := name +func (s *projectService) FindOrCreateProject(ctx shared.Context, providerID string, orgID uuid.UUID, name string, externalEntityID string, parentID uuid.UUID) (*models.Project, error) { + project := &models.Project{ Name: name, Slug: slug.Make(name), OrganizationID: orgID, ParentID: &parentID, Type: models.ProjectTypeDynamic, - ExternalEntityID: &externalID, + ExternalEntityID: &externalEntityID, ExternalEntityProviderID: &providerID, } newProjects, _, err := s.projectRepository.UpsertSplit(ctx.Request().Context(), nil, providerID, []*models.Project{project}) diff --git a/shared/common_interfaces.go b/shared/common_interfaces.go index c1278be1d..f5fb0aeaa 100644 --- a/shared/common_interfaces.go +++ b/shared/common_interfaces.go @@ -106,7 +106,7 @@ type ProjectRepository interface { ListSubProjectsAndAssets(ctx context.Context, tx DB, allowedAssetIDs []string, allowedProjectIDs []uuid.UUID, parentID *uuid.UUID, orgID uuid.UUID, pageInfo PageInfo, search string, filter []FilterQuery, sort []SortQuery) (Paged[dtos.ProjectAssetDTO], error) SearchProjectsWithSubProjectsAndAssetsPaged(ctx context.Context, tx DB, allowedAssetIDs []string, allowedProjectIDs []string, parentID *uuid.UUID, orgID uuid.UUID, pageInfo PageInfo, search string, filter []FilterQuery, sort []SortQuery) (Paged[dtos.ProjectDTO], error) All(ctx context.Context, tx DB) ([]models.Project, error) - CleanupDynamicProject(ctx context.Context, tx DB, organizationID uuid.UUID, parentProjectID uuid.UUID, projectName string, assetName string, assetVersionName string) error + CleanupDynamicProject(ctx context.Context, tx DB, organizationID uuid.UUID, parentProjectID uuid.UUID, providerID string, projectExternalEntityID string, assetExternalEntityID string, assetVersionName string) error } type Verifier interface { @@ -378,7 +378,7 @@ type ProjectService interface { CreateProject(ctx Context, project *models.Project) error BootstrapProject(ctx context.Context, rbac AccessControl, project *models.Project) error SearchProjectsWithSubProjectsAndAssetsPaged(c Context) (Paged[dtos.ProjectDTO], error) - FindOrCreateProject(ctx Context, providerID string, orgID uuid.UUID, name string, parentID uuid.UUID) (*models.Project, error) + FindOrCreateProject(ctx Context, providerID string, orgID uuid.UUID, name string, externalEntityID string, parentID uuid.UUID) (*models.Project, error) } type InTotoVerifierService interface { @@ -393,7 +393,7 @@ type AssetService interface { GetCVSSBadgeSVG(ctx context.Context, latest *models.ArtifactRiskHistory) string CreateAsset(ctx context.Context, rbac AccessControl, currentUserID string, asset models.Asset) (*models.Asset, error) BootstrapAsset(ctx context.Context, rbac AccessControl, asset *models.Asset) error - FindOrCreateAsset(ctx context.Context, rbac AccessControl, providerID string, orgID uuid.UUID, projectID uuid.UUID, name string, currentUser string) (*models.Asset, error) + FindOrCreateAsset(ctx context.Context, rbac AccessControl, providerID string, orgID uuid.UUID, projectID uuid.UUID, name string, externalEntityID string, currentUser string) (*models.Asset, error) } type ArtifactService interface { GetArtifactsByAssetIDAndAssetVersionName(ctx context.Context, tx DB, assetID uuid.UUID, assetVersionName string) ([]models.Artifact, error) From ac6abab6e1f110657e4b3fa5af6e541ee8b084df Mon Sep 17 00:00:00 2001 From: rafi Date: Tue, 16 Jun 2026 14:04:03 +0200 Subject: [PATCH 06/10] fix lint Signed-off-by: rafi --- controllers/project_controller.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/controllers/project_controller.go b/controllers/project_controller.go index 7cbe263f2..3a65fe097 100644 --- a/controllers/project_controller.go +++ b/controllers/project_controller.go @@ -569,6 +569,9 @@ func (ProjectController *ProjectController) HandleDynamicProject(ctx shared.Cont } project, err := ProjectController.projectService.FindOrCreateProject(ctx, providerID, organization.GetID(), projectName, projectExternalEntityID, parentProject.ID) + if err != nil { + return echo.NewHTTPError(500, fmt.Sprintf("could not create project: %s", err.Error())).WithInternal(err) + } rbac := shared.GetRBAC(ctx) asset, err := ProjectController.assetService.FindOrCreateAsset(ctx.Request().Context(), rbac, providerID, organization.GetID(), project.ID, assetName, assetExternalEntityID, userID) From 1a5ebbfd52e8fc040e18ed08106bf4799592abd2 Mon Sep 17 00:00:00 2001 From: rafi Date: Tue, 16 Jun 2026 14:04:52 +0200 Subject: [PATCH 07/10] fix mocks Signed-off-by: rafi --- mocks/mock_AssetService.go | 30 +++++++++++++++++------------ mocks/mock_ProjectRepository.go | 26 +++++++++++++++---------- mocks/mock_ProjectService.go | 34 +++++++++++++++++++-------------- 3 files changed, 54 insertions(+), 36 deletions(-) diff --git a/mocks/mock_AssetService.go b/mocks/mock_AssetService.go index b5ff0663f..5634b418a 100644 --- a/mocks/mock_AssetService.go +++ b/mocks/mock_AssetService.go @@ -184,8 +184,8 @@ func (_c *AssetService_CreateAsset_Call) RunAndReturn(run func(ctx context.Conte } // FindOrCreateAsset provides a mock function for the type AssetService -func (_mock *AssetService) FindOrCreateAsset(ctx context.Context, rbac shared.AccessControl, providerID string, orgID uuid.UUID, projectID uuid.UUID, name string, currentUser string) (*models.Asset, error) { - ret := _mock.Called(ctx, rbac, providerID, orgID, projectID, name, currentUser) +func (_mock *AssetService) FindOrCreateAsset(ctx context.Context, rbac shared.AccessControl, providerID string, orgID uuid.UUID, projectID uuid.UUID, name string, externalEntityID string, currentUser string) (*models.Asset, error) { + ret := _mock.Called(ctx, rbac, providerID, orgID, projectID, name, externalEntityID, currentUser) if len(ret) == 0 { panic("no return value specified for FindOrCreateAsset") @@ -193,18 +193,18 @@ func (_mock *AssetService) FindOrCreateAsset(ctx context.Context, rbac shared.Ac var r0 *models.Asset var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, shared.AccessControl, string, uuid.UUID, uuid.UUID, string, string) (*models.Asset, error)); ok { - return returnFunc(ctx, rbac, providerID, orgID, projectID, name, currentUser) + if returnFunc, ok := ret.Get(0).(func(context.Context, shared.AccessControl, string, uuid.UUID, uuid.UUID, string, string, string) (*models.Asset, error)); ok { + return returnFunc(ctx, rbac, providerID, orgID, projectID, name, externalEntityID, currentUser) } - if returnFunc, ok := ret.Get(0).(func(context.Context, shared.AccessControl, string, uuid.UUID, uuid.UUID, string, string) *models.Asset); ok { - r0 = returnFunc(ctx, rbac, providerID, orgID, projectID, name, currentUser) + if returnFunc, ok := ret.Get(0).(func(context.Context, shared.AccessControl, string, uuid.UUID, uuid.UUID, string, string, string) *models.Asset); ok { + r0 = returnFunc(ctx, rbac, providerID, orgID, projectID, name, externalEntityID, currentUser) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*models.Asset) } } - if returnFunc, ok := ret.Get(1).(func(context.Context, shared.AccessControl, string, uuid.UUID, uuid.UUID, string, string) error); ok { - r1 = returnFunc(ctx, rbac, providerID, orgID, projectID, name, currentUser) + if returnFunc, ok := ret.Get(1).(func(context.Context, shared.AccessControl, string, uuid.UUID, uuid.UUID, string, string, string) error); ok { + r1 = returnFunc(ctx, rbac, providerID, orgID, projectID, name, externalEntityID, currentUser) } else { r1 = ret.Error(1) } @@ -223,12 +223,13 @@ type AssetService_FindOrCreateAsset_Call struct { // - orgID uuid.UUID // - projectID uuid.UUID // - name string +// - externalEntityID string // - currentUser string -func (_e *AssetService_Expecter) FindOrCreateAsset(ctx interface{}, rbac interface{}, providerID interface{}, orgID interface{}, projectID interface{}, name interface{}, currentUser interface{}) *AssetService_FindOrCreateAsset_Call { - return &AssetService_FindOrCreateAsset_Call{Call: _e.mock.On("FindOrCreateAsset", ctx, rbac, providerID, orgID, projectID, name, currentUser)} +func (_e *AssetService_Expecter) FindOrCreateAsset(ctx interface{}, rbac interface{}, providerID interface{}, orgID interface{}, projectID interface{}, name interface{}, externalEntityID interface{}, currentUser interface{}) *AssetService_FindOrCreateAsset_Call { + return &AssetService_FindOrCreateAsset_Call{Call: _e.mock.On("FindOrCreateAsset", ctx, rbac, providerID, orgID, projectID, name, externalEntityID, currentUser)} } -func (_c *AssetService_FindOrCreateAsset_Call) Run(run func(ctx context.Context, rbac shared.AccessControl, providerID string, orgID uuid.UUID, projectID uuid.UUID, name string, currentUser string)) *AssetService_FindOrCreateAsset_Call { +func (_c *AssetService_FindOrCreateAsset_Call) Run(run func(ctx context.Context, rbac shared.AccessControl, providerID string, orgID uuid.UUID, projectID uuid.UUID, name string, externalEntityID string, currentUser string)) *AssetService_FindOrCreateAsset_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -258,6 +259,10 @@ func (_c *AssetService_FindOrCreateAsset_Call) Run(run func(ctx context.Context, if args[6] != nil { arg6 = args[6].(string) } + var arg7 string + if args[7] != nil { + arg7 = args[7].(string) + } run( arg0, arg1, @@ -266,6 +271,7 @@ func (_c *AssetService_FindOrCreateAsset_Call) Run(run func(ctx context.Context, arg4, arg5, arg6, + arg7, ) }) return _c @@ -276,7 +282,7 @@ func (_c *AssetService_FindOrCreateAsset_Call) Return(asset *models.Asset, err e return _c } -func (_c *AssetService_FindOrCreateAsset_Call) RunAndReturn(run func(ctx context.Context, rbac shared.AccessControl, providerID string, orgID uuid.UUID, projectID uuid.UUID, name string, currentUser string) (*models.Asset, error)) *AssetService_FindOrCreateAsset_Call { +func (_c *AssetService_FindOrCreateAsset_Call) RunAndReturn(run func(ctx context.Context, rbac shared.AccessControl, providerID string, orgID uuid.UUID, projectID uuid.UUID, name string, externalEntityID string, currentUser string) (*models.Asset, error)) *AssetService_FindOrCreateAsset_Call { _c.Call.Return(run) return _c } diff --git a/mocks/mock_ProjectRepository.go b/mocks/mock_ProjectRepository.go index 91c5c9b8f..5b7f6cb78 100644 --- a/mocks/mock_ProjectRepository.go +++ b/mocks/mock_ProjectRepository.go @@ -174,16 +174,16 @@ func (_c *ProjectRepository_All_Call) RunAndReturn(run func(ctx context.Context, } // CleanupDynamicProject provides a mock function for the type ProjectRepository -func (_mock *ProjectRepository) CleanupDynamicProject(ctx context.Context, tx shared.DB, organizationID uuid.UUID, parentProjectID uuid.UUID, projectName string, assetName string, assetVersionName string) error { - ret := _mock.Called(ctx, tx, organizationID, parentProjectID, projectName, assetName, assetVersionName) +func (_mock *ProjectRepository) CleanupDynamicProject(ctx context.Context, tx shared.DB, organizationID uuid.UUID, parentProjectID uuid.UUID, providerID string, projectExternalEntityID string, assetExternalEntityID string, assetVersionName string) error { + ret := _mock.Called(ctx, tx, organizationID, parentProjectID, providerID, projectExternalEntityID, assetExternalEntityID, assetVersionName) if len(ret) == 0 { panic("no return value specified for CleanupDynamicProject") } var r0 error - if returnFunc, ok := ret.Get(0).(func(context.Context, shared.DB, uuid.UUID, uuid.UUID, string, string, string) error); ok { - r0 = returnFunc(ctx, tx, organizationID, parentProjectID, projectName, assetName, assetVersionName) + if returnFunc, ok := ret.Get(0).(func(context.Context, shared.DB, uuid.UUID, uuid.UUID, string, string, string, string) error); ok { + r0 = returnFunc(ctx, tx, organizationID, parentProjectID, providerID, projectExternalEntityID, assetExternalEntityID, assetVersionName) } else { r0 = ret.Error(0) } @@ -200,14 +200,15 @@ type ProjectRepository_CleanupDynamicProject_Call struct { // - tx shared.DB // - organizationID uuid.UUID // - parentProjectID uuid.UUID -// - projectName string -// - assetName string +// - providerID string +// - projectExternalEntityID string +// - assetExternalEntityID string // - assetVersionName string -func (_e *ProjectRepository_Expecter) CleanupDynamicProject(ctx interface{}, tx interface{}, organizationID interface{}, parentProjectID interface{}, projectName interface{}, assetName interface{}, assetVersionName interface{}) *ProjectRepository_CleanupDynamicProject_Call { - return &ProjectRepository_CleanupDynamicProject_Call{Call: _e.mock.On("CleanupDynamicProject", ctx, tx, organizationID, parentProjectID, projectName, assetName, assetVersionName)} +func (_e *ProjectRepository_Expecter) CleanupDynamicProject(ctx interface{}, tx interface{}, organizationID interface{}, parentProjectID interface{}, providerID interface{}, projectExternalEntityID interface{}, assetExternalEntityID interface{}, assetVersionName interface{}) *ProjectRepository_CleanupDynamicProject_Call { + return &ProjectRepository_CleanupDynamicProject_Call{Call: _e.mock.On("CleanupDynamicProject", ctx, tx, organizationID, parentProjectID, providerID, projectExternalEntityID, assetExternalEntityID, assetVersionName)} } -func (_c *ProjectRepository_CleanupDynamicProject_Call) Run(run func(ctx context.Context, tx shared.DB, organizationID uuid.UUID, parentProjectID uuid.UUID, projectName string, assetName string, assetVersionName string)) *ProjectRepository_CleanupDynamicProject_Call { +func (_c *ProjectRepository_CleanupDynamicProject_Call) Run(run func(ctx context.Context, tx shared.DB, organizationID uuid.UUID, parentProjectID uuid.UUID, providerID string, projectExternalEntityID string, assetExternalEntityID string, assetVersionName string)) *ProjectRepository_CleanupDynamicProject_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -237,6 +238,10 @@ func (_c *ProjectRepository_CleanupDynamicProject_Call) Run(run func(ctx context if args[6] != nil { arg6 = args[6].(string) } + var arg7 string + if args[7] != nil { + arg7 = args[7].(string) + } run( arg0, arg1, @@ -245,6 +250,7 @@ func (_c *ProjectRepository_CleanupDynamicProject_Call) Run(run func(ctx context arg4, arg5, arg6, + arg7, ) }) return _c @@ -255,7 +261,7 @@ func (_c *ProjectRepository_CleanupDynamicProject_Call) Return(err error) *Proje return _c } -func (_c *ProjectRepository_CleanupDynamicProject_Call) RunAndReturn(run func(ctx context.Context, tx shared.DB, organizationID uuid.UUID, parentProjectID uuid.UUID, projectName string, assetName string, assetVersionName string) error) *ProjectRepository_CleanupDynamicProject_Call { +func (_c *ProjectRepository_CleanupDynamicProject_Call) RunAndReturn(run func(ctx context.Context, tx shared.DB, organizationID uuid.UUID, parentProjectID uuid.UUID, providerID string, projectExternalEntityID string, assetExternalEntityID string, assetVersionName string) error) *ProjectRepository_CleanupDynamicProject_Call { _c.Call.Return(run) return _c } diff --git a/mocks/mock_ProjectService.go b/mocks/mock_ProjectService.go index 15dd6b62c..59f53c1ba 100644 --- a/mocks/mock_ProjectService.go +++ b/mocks/mock_ProjectService.go @@ -162,8 +162,8 @@ func (_c *ProjectService_CreateProject_Call) RunAndReturn(run func(ctx shared.Co } // FindOrCreateProject provides a mock function for the type ProjectService -func (_mock *ProjectService) FindOrCreateProject(ctx shared.Context, providerID string, orgID uuid.UUID, name string, parentID uuid.UUID) (*models.Project, error) { - ret := _mock.Called(ctx, providerID, orgID, name, parentID) +func (_mock *ProjectService) FindOrCreateProject(ctx shared.Context, providerID string, orgID uuid.UUID, name string, externalEntityID string, parentID uuid.UUID) (*models.Project, error) { + ret := _mock.Called(ctx, providerID, orgID, name, externalEntityID, parentID) if len(ret) == 0 { panic("no return value specified for FindOrCreateProject") @@ -171,18 +171,18 @@ func (_mock *ProjectService) FindOrCreateProject(ctx shared.Context, providerID var r0 *models.Project var r1 error - if returnFunc, ok := ret.Get(0).(func(shared.Context, string, uuid.UUID, string, uuid.UUID) (*models.Project, error)); ok { - return returnFunc(ctx, providerID, orgID, name, parentID) + if returnFunc, ok := ret.Get(0).(func(shared.Context, string, uuid.UUID, string, string, uuid.UUID) (*models.Project, error)); ok { + return returnFunc(ctx, providerID, orgID, name, externalEntityID, parentID) } - if returnFunc, ok := ret.Get(0).(func(shared.Context, string, uuid.UUID, string, uuid.UUID) *models.Project); ok { - r0 = returnFunc(ctx, providerID, orgID, name, parentID) + if returnFunc, ok := ret.Get(0).(func(shared.Context, string, uuid.UUID, string, string, uuid.UUID) *models.Project); ok { + r0 = returnFunc(ctx, providerID, orgID, name, externalEntityID, parentID) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*models.Project) } } - if returnFunc, ok := ret.Get(1).(func(shared.Context, string, uuid.UUID, string, uuid.UUID) error); ok { - r1 = returnFunc(ctx, providerID, orgID, name, parentID) + if returnFunc, ok := ret.Get(1).(func(shared.Context, string, uuid.UUID, string, string, uuid.UUID) error); ok { + r1 = returnFunc(ctx, providerID, orgID, name, externalEntityID, parentID) } else { r1 = ret.Error(1) } @@ -199,12 +199,13 @@ type ProjectService_FindOrCreateProject_Call struct { // - providerID string // - orgID uuid.UUID // - name string +// - externalEntityID string // - parentID uuid.UUID -func (_e *ProjectService_Expecter) FindOrCreateProject(ctx interface{}, providerID interface{}, orgID interface{}, name interface{}, parentID interface{}) *ProjectService_FindOrCreateProject_Call { - return &ProjectService_FindOrCreateProject_Call{Call: _e.mock.On("FindOrCreateProject", ctx, providerID, orgID, name, parentID)} +func (_e *ProjectService_Expecter) FindOrCreateProject(ctx interface{}, providerID interface{}, orgID interface{}, name interface{}, externalEntityID interface{}, parentID interface{}) *ProjectService_FindOrCreateProject_Call { + return &ProjectService_FindOrCreateProject_Call{Call: _e.mock.On("FindOrCreateProject", ctx, providerID, orgID, name, externalEntityID, parentID)} } -func (_c *ProjectService_FindOrCreateProject_Call) Run(run func(ctx shared.Context, providerID string, orgID uuid.UUID, name string, parentID uuid.UUID)) *ProjectService_FindOrCreateProject_Call { +func (_c *ProjectService_FindOrCreateProject_Call) Run(run func(ctx shared.Context, providerID string, orgID uuid.UUID, name string, externalEntityID string, parentID uuid.UUID)) *ProjectService_FindOrCreateProject_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 shared.Context if args[0] != nil { @@ -222,9 +223,13 @@ func (_c *ProjectService_FindOrCreateProject_Call) Run(run func(ctx shared.Conte if args[3] != nil { arg3 = args[3].(string) } - var arg4 uuid.UUID + var arg4 string if args[4] != nil { - arg4 = args[4].(uuid.UUID) + arg4 = args[4].(string) + } + var arg5 uuid.UUID + if args[5] != nil { + arg5 = args[5].(uuid.UUID) } run( arg0, @@ -232,6 +237,7 @@ func (_c *ProjectService_FindOrCreateProject_Call) Run(run func(ctx shared.Conte arg2, arg3, arg4, + arg5, ) }) return _c @@ -242,7 +248,7 @@ func (_c *ProjectService_FindOrCreateProject_Call) Return(project *models.Projec return _c } -func (_c *ProjectService_FindOrCreateProject_Call) RunAndReturn(run func(ctx shared.Context, providerID string, orgID uuid.UUID, name string, parentID uuid.UUID) (*models.Project, error)) *ProjectService_FindOrCreateProject_Call { +func (_c *ProjectService_FindOrCreateProject_Call) RunAndReturn(run func(ctx shared.Context, providerID string, orgID uuid.UUID, name string, externalEntityID string, parentID uuid.UUID) (*models.Project, error)) *ProjectService_FindOrCreateProject_Call { _c.Call.Return(run) return _c } From ce53a8bc801c2bcfa15fb8a36673d8d92ea5325b Mon Sep 17 00:00:00 2001 From: rafi Date: Tue, 16 Jun 2026 17:43:22 +0200 Subject: [PATCH 08/10] create and populate release on dynamic project handling Signed-off-by: rafi --- controllers/project_controller.go | 23 ++++++++++++++++++++- database/repositories/release_repository.go | 19 +++++++++++++++++ services/release_service.go | 4 ++++ shared/common_interfaces.go | 2 ++ 4 files changed, 47 insertions(+), 1 deletion(-) diff --git a/controllers/project_controller.go b/controllers/project_controller.go index 3a65fe097..855a4aaa4 100644 --- a/controllers/project_controller.go +++ b/controllers/project_controller.go @@ -39,12 +39,13 @@ type ProjectController struct { artifactRepository shared.ArtifactRepository assetVersionService shared.AssetVersionService assetService shared.AssetService + releaseService shared.ReleaseService projectService shared.ProjectService webhookRepository shared.WebhookIntegrationRepository scanService shared.ScanService } -func NewProjectController(repository shared.ProjectRepository, assetRepository shared.AssetRepository, assetVersionRepository shared.AssetVersionRepository, artifactRepository shared.ArtifactRepository, assetVersionService shared.AssetVersionService, assetService shared.AssetService, projectService shared.ProjectService, webhookRepository shared.WebhookIntegrationRepository, scanService shared.ScanService) *ProjectController { +func NewProjectController(repository shared.ProjectRepository, assetRepository shared.AssetRepository, assetVersionRepository shared.AssetVersionRepository, artifactRepository shared.ArtifactRepository, assetVersionService shared.AssetVersionService, assetService shared.AssetService, releaseService shared.ReleaseService, projectService shared.ProjectService, webhookRepository shared.WebhookIntegrationRepository, scanService shared.ScanService) *ProjectController { return &ProjectController{ projectRepository: repository, assetRepository: assetRepository, @@ -52,6 +53,7 @@ func NewProjectController(repository shared.ProjectRepository, assetRepository s artifactRepository: artifactRepository, assetVersionService: assetVersionService, assetService: assetService, + releaseService: releaseService, projectService: projectService, webhookRepository: webhookRepository, scanService: scanService, @@ -596,6 +598,25 @@ func (ProjectController *ProjectController) HandleDynamicProject(ctx shared.Cont return ctx.JSON(500, map[string]string{"error": "could not save artifact"}) } + release, err := ProjectController.releaseService.FindOrCreate(ctx.Request().Context(), parentProject.ID, providerID) + if err != nil { + return echo.NewHTTPError(500, fmt.Sprintf("could not create release: %s", err.Error())).WithInternal(err) + } + + //add or update release item + releaseItem := models.ReleaseItem{ + ReleaseID: release.ID, + ArtifactName: &artifact.ArtifactName, + AssetID: &asset.ID, + AssetVersionName: &assetVersion.Name, + } + + err = ProjectController.releaseService.AddItem(ctx.Request().Context(), &releaseItem) + if err != nil { + slog.Error("could not add release item", "err", err) + return ctx.JSON(500, map[string]string{"error": "could not add release item"}) + } + normalized, err := normalize.SBOMGraphFromCycloneDX(bom, artifactName, "operator", asset.KeepOriginalSbomRootComponent) if err != nil { slog.Error("trivy operator: failed to normalize BOM", "err", err) diff --git a/database/repositories/release_repository.go b/database/repositories/release_repository.go index 2bc1d3bb7..42b5dda8a 100644 --- a/database/repositories/release_repository.go +++ b/database/repositories/release_repository.go @@ -252,3 +252,22 @@ func (r *releaseRepository) GetCandidateItemsForRelease(ctx context.Context, tx return artifacts, rels, nil } + +func (r *releaseRepository) FindOrCreate(ctx context.Context, tx *gorm.DB, projectID uuid.UUID, name string) (models.Release, error) { + var rel models.Release + err := r.GetDB(ctx, tx).Where("project_id = ? AND name = ?", projectID, name).First(&rel).Error + if err == nil { + return rel, nil + } + if err != nil && err != gorm.ErrRecordNotFound { + return models.Release{}, err + } + + rel = models.Release{ + Name: name, + ProjectID: projectID, + } + + err = r.GetDB(ctx, tx).Create(&rel).Error + return rel, err +} diff --git a/services/release_service.go b/services/release_service.go index ebb473be3..ced0aef66 100644 --- a/services/release_service.go +++ b/services/release_service.go @@ -36,6 +36,10 @@ func (s *releaseService) ReadRecursive(ctx context.Context, id uuid.UUID) (model return s.releaseRepository.ReadRecursive(ctx, nil, id) } +func (s *releaseService) FindOrCreate(ctx context.Context, projectID uuid.UUID, name string) (models.Release, error) { + return s.releaseRepository.FindOrCreate(ctx, nil, projectID, name) +} + func (s *releaseService) Create(ctx context.Context, r *models.Release) error { return s.releaseRepository.Create(ctx, nil, r) } diff --git a/shared/common_interfaces.go b/shared/common_interfaces.go index f5fb0aeaa..47cb163a8 100644 --- a/shared/common_interfaces.go +++ b/shared/common_interfaces.go @@ -66,6 +66,7 @@ type ReleaseService interface { AddItem(ctx context.Context, item *models.ReleaseItem) error RemoveItem(ctx context.Context, id uuid.UUID) error ListCandidates(ctx context.Context, projectID uuid.UUID, releaseID *uuid.UUID) ([]models.Artifact, []models.Release, error) + FindOrCreate(ctx context.Context, projectID uuid.UUID, name string) (models.Release, error) } type PersonalAccessTokenService interface { @@ -187,6 +188,7 @@ type ReleaseRepository interface { CreateReleaseItem(ctx context.Context, tx DB, item *models.ReleaseItem) error DeleteReleaseItem(ctx context.Context, tx DB, id uuid.UUID) error GetCandidateItemsForRelease(ctx context.Context, tx DB, projectID uuid.UUID, releaseID *uuid.UUID) ([]models.Artifact, []models.Release, error) + FindOrCreate(ctx context.Context, tx DB, projectID uuid.UUID, name string) (models.Release, error) } type CveRepository interface { From 7ce637e3f3428bab817636ddfbd14a166a3f0871 Mon Sep 17 00:00:00 2001 From: rafi Date: Thu, 18 Jun 2026 16:53:15 +0200 Subject: [PATCH 09/10] update dynamic project handling with release and subproject support Signed-off-by: rafi --- controllers/project_controller.go | 204 ++++++++++++-- database/repositories/artifact_repository.go | 20 ++ database/repositories/asset_repository.go | 9 + .../repositories/asset_version_repository.go | 6 + database/repositories/project_repository.go | 39 ++- dtos/project_dto.go | 44 ++- mocks/mock_ArtifactRepository.go | 148 ++++++++++ mocks/mock_AssetRepository.go | 154 ++++++++++ mocks/mock_AssetService.go | 30 +- mocks/mock_AssetVersionRepository.go | 74 +++++ mocks/mock_ProjectRepository.go | 266 +++++++++++++++++- mocks/mock_ProjectService.go | 30 +- mocks/mock_ReleaseRepository.go | 78 +++++ mocks/mock_ReleaseService.go | 72 +++++ services/asset_service.go | 3 +- services/project_service.go | 3 +- shared/common_interfaces.go | 14 +- 17 files changed, 1112 insertions(+), 82 deletions(-) diff --git a/controllers/project_controller.go b/controllers/project_controller.go index 855a4aaa4..0e40cb8fa 100644 --- a/controllers/project_controller.go +++ b/controllers/project_controller.go @@ -22,6 +22,7 @@ import ( "log/slog" cdx "github.com/CycloneDX/cyclonedx-go" + "github.com/google/uuid" "github.com/l3montree-dev/devguard/database/models" "github.com/l3montree-dev/devguard/dtos" "github.com/l3montree-dev/devguard/normalize" @@ -542,16 +543,36 @@ func (ProjectController *ProjectController) HandleDynamicProject(ctx shared.Cont action := probe.Verb projectName := probe.ProjectName projectExternalEntityID := probe.ProjectExternalEntityID + projectDescription := probe.ProjectDescription + + subProjectExternalEntityID := probe.SubProjectExternalEntityID + subProjectName := probe.SubProjectName + subProjectDescription := probe.SubProjectDescription + assetName := probe.AssetName assetExternalEntityID := probe.AssetExternalEntityID - assetVersionName := probe.AssetVersion + assetDescription := probe.AssetDescription + + assetVersionName := probe.AssetVersionName + artifactName := probe.Artifact + providerID := shared.GetProviderID(ctx) organization := shared.GetOrg(ctx) parentProject := shared.GetProject(ctx) userID := shared.GetSession(ctx).GetUserID() if action == "delete" { - err := ProjectController.projectRepository.CleanupDynamicProject(ctx.Request().Context(), nil, organization.GetID(), parentProject.ID, providerID, projectExternalEntityID, assetExternalEntityID, assetVersionName) + parentProjectID := parentProject.ID + proExternalEntityID := projectExternalEntityID + if subProjectExternalEntityID != "" { + subProjectParent, err := ProjectController.projectRepository.GetDirectChildProjectsWithProviderIDAndExternalEntityID(ctx.Request().Context(), nil, parentProject.ID, providerID, projectExternalEntityID) + if err != nil { + return echo.NewHTTPError(500, fmt.Sprintf("could not fetch sub-projects: %s", err.Error())).WithInternal(err) + } + parentProjectID = subProjectParent.ID + proExternalEntityID = subProjectExternalEntityID + } + err := ProjectController.projectRepository.CleanupDynamicProject(ctx.Request().Context(), nil, organization.GetID(), parentProjectID, providerID, proExternalEntityID, assetExternalEntityID, assetVersionName, artifactName) if err != nil { return echo.NewHTTPError(500, fmt.Sprintf("could not delete project: %s", err.Error())).WithInternal(err) } @@ -570,13 +591,23 @@ func (ProjectController *ProjectController) HandleDynamicProject(ctx shared.Cont return echo.NewHTTPError(400, fmt.Sprintf("could not parse CycloneDX BOM: %s", err.Error())).WithInternal(err) } - project, err := ProjectController.projectService.FindOrCreateProject(ctx, providerID, organization.GetID(), projectName, projectExternalEntityID, parentProject.ID) + project, err := ProjectController.projectService.FindOrCreateProject(ctx, providerID, organization.GetID(), projectName, projectExternalEntityID, parentProject.ID, projectDescription) if err != nil { return echo.NewHTTPError(500, fmt.Sprintf("could not create project: %s", err.Error())).WithInternal(err) } + pID := project.ID + + if subProjectExternalEntityID != "" { + subProject, err := ProjectController.projectService.FindOrCreateProject(ctx, providerID, organization.GetID(), subProjectName, subProjectExternalEntityID, project.ID, subProjectDescription) + if err != nil { + return echo.NewHTTPError(500, fmt.Sprintf("could not create sub-project: %s", err.Error())).WithInternal(err) + } + pID = subProject.ID + } + rbac := shared.GetRBAC(ctx) - asset, err := ProjectController.assetService.FindOrCreateAsset(ctx.Request().Context(), rbac, providerID, organization.GetID(), project.ID, assetName, assetExternalEntityID, userID) + asset, err := ProjectController.assetService.FindOrCreateAsset(ctx.Request().Context(), rbac, providerID, organization.GetID(), pID, assetName, assetExternalEntityID, userID, assetDescription) if err != nil { return echo.NewHTTPError(500, fmt.Sprintf("could not create asset: %s", err.Error())).WithInternal(err) } @@ -586,8 +617,6 @@ func (ProjectController *ProjectController) HandleDynamicProject(ctx shared.Cont return echo.NewHTTPError(500, fmt.Sprintf("could not create asset version: %s", err.Error())).WithInternal(err) } - artifactName := normalize.ArtifactPurl("operator", fmt.Sprintf("%s/%s/%s", organization.Slug, project.Slug, asset.Name)) - artifact := models.Artifact{ ArtifactName: artifactName, AssetVersionName: assetVersion.Name, @@ -646,46 +675,161 @@ func (ProjectController *ProjectController) HandleDynamicProject(ctx shared.Cont func (ProjectController *ProjectController) ListDynamicProjects(ctx shared.Context) error { reqCtx := ctx.Request().Context() parentProject := shared.GetProject(ctx) - providerID := shared.GetProviderID(ctx) - //TOD:: we should optimize this by doing it in a single query. - projects, err := ProjectController.projectRepository.GetDirectChildProjects(reqCtx, nil, parentProject.ID) + // Query 1: direct child projects filtered by providerID + projects, err := ProjectController.projectRepository.GetDirectChildProjectsWithProviderID(reqCtx, nil, parentProject.ID, providerID) if err != nil { return echo.NewHTTPError(500, "could not list projects").WithInternal(err) } + if len(projects) == 0 { + return ctx.JSON(200, []dtos.ProjectsAssetAssetVersionsDTO{}) + } - result := make([]dtos.ProjectsAssetAssetVersionsDTO, 0, len(projects)) - for _, project := range projects { - if project.ExternalEntityProviderID == nil || *project.ExternalEntityProviderID != providerID { - continue - } - assets, err := ProjectController.assetRepository.GetByProjectID(reqCtx, nil, project.ID) - if err != nil { - return echo.NewHTTPError(500, "could not list assets").WithInternal(err) + projectIDs := make([]uuid.UUID, len(projects)) + for i, p := range projects { + projectIDs[i] = p.ID + } + + // Query 2: all sub-projects for all parent projects in one shot + subProjects, err := ProjectController.projectRepository.GetChildProjectsForParents(reqCtx, nil, projectIDs, providerID) + if err != nil { + return echo.NewHTTPError(500, "could not list sub-projects").WithInternal(err) + } + + subProjectIDs := make([]uuid.UUID, len(subProjects)) + for i, sp := range subProjects { + subProjectIDs[i] = sp.ID + } + + // Query 3: all assets for projects + sub-projects, filtered by providerID + allProjectIDs := append(projectIDs, subProjectIDs...) + allAssets, err := ProjectController.assetRepository.GetByProjectIDsWithProviderID(reqCtx, nil, allProjectIDs, providerID) + if err != nil { + return echo.NewHTTPError(500, "could not list assets").WithInternal(err) + } + + allAssetIDs := make([]uuid.UUID, len(allAssets)) + for i, a := range allAssets { + allAssetIDs[i] = a.ID + } + + // Query 4: all asset versions for all assets + allAssetVersions, err := ProjectController.assetVersionRepository.GetAssetVersionsByAssetIDs(reqCtx, nil, allAssetIDs) + if err != nil { + return echo.NewHTTPError(500, "could not list asset versions").WithInternal(err) + } + + // Query 5: all artifacts for all assets + allArtifacts, err := ProjectController.artifactRepository.GetByAssetIDs(reqCtx, nil, allAssetIDs) + if err != nil { + return echo.NewHTTPError(500, "could not list artifacts").WithInternal(err) + } + + // Build in-memory lookup maps + subProjectsByParentID := make(map[uuid.UUID][]models.Project) + for _, sp := range subProjects { + if sp.ParentID != nil { + subProjectsByParentID[*sp.ParentID] = append(subProjectsByParentID[*sp.ParentID], sp) } + } + + assetsByProjectID := make(map[uuid.UUID][]models.Asset) + for _, a := range allAssets { + assetsByProjectID[a.ProjectID] = append(assetsByProjectID[a.ProjectID], a) + } - entry := dtos.ProjectsAssetAssetVersionsDTO{ProjectExternalEntityID: *project.ExternalEntityID, ProjectName: project.Name} - for _, asset := range assets { - if asset.ExternalEntityProviderID == nil || *asset.ExternalEntityProviderID != providerID { + versionsByAssetID := make(map[uuid.UUID][]models.AssetVersion) + for _, av := range allAssetVersions { + versionsByAssetID[av.AssetID] = append(versionsByAssetID[av.AssetID], av) + } + + artifactsByAssetIDAndVersion := make(map[uuid.UUID]map[string][]string) + for _, art := range allArtifacts { + if artifactsByAssetIDAndVersion[art.AssetID] == nil { + artifactsByAssetIDAndVersion[art.AssetID] = make(map[string][]string) + } + artifactsByAssetIDAndVersion[art.AssetID][art.AssetVersionName] = append( + artifactsByAssetIDAndVersion[art.AssetID][art.AssetVersionName], art.ArtifactName, + ) + } + + buildAssetEntries := func(projectID uuid.UUID) []struct { + AssetExternalEntityID string `json:"assetExternalEntityId"` + AssetName string `json:"assetName"` + AssetVersions []struct { + AssetVersionName string `json:"assetVersionName"` + Artifacts []string `json:"artifacts"` + } `json:"assetVersions"` + } { + var entries []struct { + AssetExternalEntityID string `json:"assetExternalEntityId"` + AssetName string `json:"assetName"` + AssetVersions []struct { + AssetVersionName string `json:"assetVersionName"` + Artifacts []string `json:"artifacts"` + } `json:"assetVersions"` + } + for _, asset := range assetsByProjectID[projectID] { + if asset.ExternalEntityID == nil { continue } - versions, err := ProjectController.assetVersionRepository.GetAssetVersionsByAssetID(reqCtx, nil, asset.ID) - if err != nil { - return echo.NewHTTPError(500, "could not list asset versions").WithInternal(err) + versions := versionsByAssetID[asset.ID] + if len(versions) == 0 { + continue } - assetEntry := struct { - AssetExternalEntityID string `json:"assetExternalEntityId"` - AssetName string `json:"assetName"` - Versions []string `json:"versions"` + AssetExternalEntityID string `json:"assetExternalEntityId"` + AssetName string `json:"assetName"` + AssetVersions []struct { + AssetVersionName string `json:"assetVersionName"` + Artifacts []string `json:"artifacts"` + } `json:"assetVersions"` }{AssetExternalEntityID: *asset.ExternalEntityID, AssetName: asset.Name} + for _, av := range versions { + avEntry := struct { + AssetVersionName string `json:"assetVersionName"` + Artifacts []string `json:"artifacts"` + }{AssetVersionName: av.Name, Artifacts: artifactsByAssetIDAndVersion[asset.ID][av.Name]} + assetEntry.AssetVersions = append(assetEntry.AssetVersions, avEntry) + } + entries = append(entries, assetEntry) + } + return entries + } + + result := make([]dtos.ProjectsAssetAssetVersionsDTO, 0, len(projects)) + for _, project := range projects { + if project.ExternalEntityID == nil { + continue + } + entry := dtos.ProjectsAssetAssetVersionsDTO{ + ProjectExternalEntityID: *project.ExternalEntityID, + ProjectName: project.Name, + } - for _, v := range versions { - assetEntry.Versions = append(assetEntry.Versions, v.Name) + for _, sp := range subProjectsByParentID[project.ID] { + if sp.ExternalEntityID == nil { + continue } - entry.Assets = append(entry.Assets, assetEntry) + spEntry := struct { + SubProjectExternalEntityID string `json:"subProjectExternalEntityId,omitempty"` + SubProjectName string `json:"subProjectName,omitempty"` + SubProjectDescription string `json:"subProjectDescription,omitempty"` + Assets []struct { + AssetExternalEntityID string `json:"assetExternalEntityId"` + AssetName string `json:"assetName"` + AssetVersions []struct { + AssetVersionName string `json:"assetVersionName"` + Artifacts []string `json:"artifacts"` + } `json:"assetVersions"` + } `json:"assets"` + }{SubProjectExternalEntityID: *sp.ExternalEntityID, SubProjectName: sp.Name} + spEntry.Assets = buildAssetEntries(sp.ID) + entry.SubProjects = append(entry.SubProjects, spEntry) } + + entry.Assets = buildAssetEntries(project.ID) result = append(result, entry) } diff --git a/database/repositories/artifact_repository.go b/database/repositories/artifact_repository.go index 9b40e7119..8994ea30f 100644 --- a/database/repositories/artifact_repository.go +++ b/database/repositories/artifact_repository.go @@ -24,6 +24,26 @@ func NewArtifactRepository(db *gorm.DB) *artifactRepository { } } +func (r *artifactRepository) GetByAssetID(ctx context.Context, tx *gorm.DB, assetID uuid.UUID) ([]models.Artifact, error) { + var artifacts []models.Artifact + err := r.GetDB(ctx, tx).Where("asset_id = ?", assetID).Find(&artifacts).Error + if err != nil { + return nil, err + } + + return artifacts, nil +} + +func (r *artifactRepository) GetByAssetIDs(ctx context.Context, tx *gorm.DB, assetIDs []uuid.UUID) ([]models.Artifact, error) { + var artifacts []models.Artifact + err := r.GetDB(ctx, tx).Where("asset_id IN ?", assetIDs).Find(&artifacts).Error + if err != nil { + return nil, err + } + + return artifacts, nil +} + func (r *artifactRepository) GetByAssetIDAndAssetVersionName(ctx context.Context, tx *gorm.DB, assetID uuid.UUID, assetVersionName string) ([]models.Artifact, error) { var artifacts []models.Artifact err := r.GetDB(ctx, tx).Where("asset_id = ? AND asset_version_name = ?", assetID, assetVersionName).Find(&artifacts).Error diff --git a/database/repositories/asset_repository.go b/database/repositories/asset_repository.go index ee60817c9..c7ff91a28 100644 --- a/database/repositories/asset_repository.go +++ b/database/repositories/asset_repository.go @@ -194,6 +194,15 @@ func (repository *assetRepository) GetByProjectIDs(ctx context.Context, tx *gorm return apps, nil } +func (repository *assetRepository) GetByProjectIDsWithProviderID(ctx context.Context, tx *gorm.DB, projectIDs []uuid.UUID, providerID string) ([]models.Asset, error) { + var apps []models.Asset + err := repository.GetDB(ctx, tx).Where("project_id IN ? AND external_entity_provider_id = ?", projectIDs, providerID).Find(&apps).Error + if err != nil { + return nil, err + } + return apps, nil +} + func (repository *assetRepository) ReadBySlug(ctx context.Context, tx *gorm.DB, projectID uuid.UUID, slug string) (models.Asset, error) { var t models.Asset err := repository.GetDB(ctx, tx).Where("slug = ? AND project_id = ?", slug, projectID).Preload("AssetVersions").First(&t).Error diff --git a/database/repositories/asset_version_repository.go b/database/repositories/asset_version_repository.go index 979805865..29404fb6c 100644 --- a/database/repositories/asset_version_repository.go +++ b/database/repositories/asset_version_repository.go @@ -236,6 +236,12 @@ func (repository *assetVersionRepository) GetAssetVersionsByAssetID(ctx context. return assets, err } +func (repository *assetVersionRepository) GetAssetVersionsByAssetIDs(ctx context.Context, tx *gorm.DB, assetIDs []uuid.UUID) ([]models.AssetVersion, error) { + var assets []models.AssetVersion + err := repository.GetDB(ctx, tx).Where("asset_id IN ?", assetIDs).Find(&assets).Error + return assets, err +} + func (repository *assetVersionRepository) GetAssetVersionsByAssetIDWithArtifacts(ctx context.Context, tx *gorm.DB, assetID uuid.UUID) ([]models.AssetVersion, error) { var assetVersion []models.AssetVersion err := repository.GetDB(ctx, tx).Preload("Artifacts").Where("asset_id = ?", assetID).Find(&assetVersion).Error diff --git a/database/repositories/project_repository.go b/database/repositories/project_repository.go index c72c7150c..9e485207f 100644 --- a/database/repositories/project_repository.go +++ b/database/repositories/project_repository.go @@ -426,6 +426,24 @@ func (g *projectRepository) GetDirectChildProjects(ctx context.Context, tx *gorm return projects, err } +func (g *projectRepository) GetDirectChildProjectsWithProviderIDAndExternalEntityID(ctx context.Context, tx *gorm.DB, parentID uuid.UUID, providerID string, externalEntityID string) (models.Project, error) { + var project models.Project + err := g.GetDB(ctx, tx).Debug().Where("parent_id = ? AND external_entity_provider_id = ? AND external_entity_id = ?", parentID, providerID, externalEntityID).First(&project).Error + return project, err +} + +func (g *projectRepository) GetDirectChildProjectsWithProviderID(ctx context.Context, tx *gorm.DB, parentID uuid.UUID, providerID string) ([]models.Project, error) { + var projects []models.Project + err := g.GetDB(ctx, tx).Where("parent_id = ? AND external_entity_provider_id = ?", parentID, providerID).Find(&projects).Error + return projects, err +} + +func (g *projectRepository) GetChildProjectsForParents(ctx context.Context, tx *gorm.DB, parentIDs []uuid.UUID, providerID string) ([]models.Project, error) { + var projects []models.Project + err := g.GetDB(ctx, tx).Where("parent_id IN ? AND external_entity_provider_id = ?", parentIDs, providerID).Find(&projects).Error + return projects, err +} + func (g *projectRepository) EnablePolicyForProject(ctx context.Context, tx *gorm.DB, projectID uuid.UUID, policyID uuid.UUID) error { return g.GetDB(ctx, tx).Model(&models.Project{ Model: models.Model{ @@ -592,8 +610,10 @@ func (g *projectRepository) Upsert(ctx context.Context, tx *gorm.DB, t *[]*model return g.GetDB(ctx, tx).Clauses(clause.OnConflict{UpdateAll: true, Columns: conflictingColumns}).Create(t).Error } -func (g *projectRepository) CleanupDynamicProject(ctx context.Context, tx *gorm.DB, organizationID uuid.UUID, parentProjectID uuid.UUID, providerID string, projectExternalEntityID string, assetExternalEntityID string, assetVersionName string) error { - query := ` +func (g *projectRepository) CleanupDynamicProject(ctx context.Context, tx *gorm.DB, organizationID uuid.UUID, parentProjectID uuid.UUID, providerID string, projectExternalEntityID string, assetExternalEntityID string, assetVersionName string, artifactName string) error { + var query string + + query = ` WITH target AS ( SELECT @@ -603,6 +623,7 @@ WITH FROM projects p JOIN assets a ON a.project_id = p.id AND a.external_entity_id = ? JOIN asset_versions av ON av.asset_id = a.id AND av.name = ? + JOIN artifacts ar ON ar.asset_version_name = av.name AND ar.asset_id = a.id WHERE p.organization_id = ? AND p.parent_id = ? AND p.external_entity_provider_id = ? @@ -610,10 +631,21 @@ WITH AND p.type = 'dynamic' LIMIT 1 ), + del_artifact AS ( + DELETE FROM artifacts + WHERE asset_id = (SELECT asset_id FROM target) + AND asset_version_name = (SELECT asset_version_name FROM target) + AND artifact_name = ? + ), del_asset_version AS ( DELETE FROM asset_versions WHERE asset_id = (SELECT asset_id FROM target) AND name = (SELECT asset_version_name FROM target) + AND NOT EXISTS ( + SELECT 1 FROM artifacts + WHERE asset_id = (SELECT asset_id FROM target) + AND asset_version_name = (SELECT asset_version_name FROM target) + ) ), del_asset AS ( DELETE FROM assets @@ -621,7 +653,6 @@ WITH AND NOT EXISTS ( SELECT 1 FROM asset_versions WHERE asset_id = (SELECT asset_id FROM target) - AND name != (SELECT asset_version_name FROM target) ) ), del_project AS ( @@ -637,6 +668,6 @@ SELECT 1` return g.GetDB(ctx, tx).Exec(query, assetExternalEntityID, assetVersionName, - organizationID, parentProjectID, providerID, projectExternalEntityID, + organizationID, parentProjectID, providerID, projectExternalEntityID, artifactName, ).Error } diff --git a/dtos/project_dto.go b/dtos/project_dto.go index e8a6341d2..f04f80dde 100644 --- a/dtos/project_dto.go +++ b/dtos/project_dto.go @@ -99,18 +99,40 @@ type ProjectAssetDTO struct { type ProjectsAssetAssetVersionsDTO struct { ProjectExternalEntityID string `json:"projectExternalEntityId"` ProjectName string `json:"projectName"` - Assets []struct { - AssetExternalEntityID string `json:"assetExternalEntityId"` - AssetName string `json:"assetName"` - Versions []string `json:"versions"` + SubProjects []struct { + SubProjectExternalEntityID string `json:"subProjectExternalEntityId,omitempty"` + SubProjectName string `json:"subProjectName,omitempty"` + SubProjectDescription string `json:"subProjectDescription,omitempty"` + Assets []struct { + AssetExternalEntityID string `json:"assetExternalEntityId"` + AssetName string `json:"assetName"` + AssetVersions []struct { + AssetVersionName string `json:"assetVersionName"` + Artifacts []string `json:"artifacts"` + } `json:"assetVersions"` + } `json:"assets"` + } `json:"subProjects,omitempty"` + Assets []struct { + AssetExternalEntityID string `json:"assetExternalEntityId"` + AssetName string `json:"assetName"` + AssetVersions []struct { + AssetVersionName string `json:"assetVersionName"` + Artifacts []string `json:"artifacts"` + } `json:"assetVersions"` } `json:"assets"` } type DynamicProjectRequestDTO struct { - Verb string `json:"verb"` - ProjectExternalEntityID string `json:"projectExternalEntityId"` - ProjectName string `json:"projectName"` - AssetExternalEntityID string `json:"assetExternalEntityId"` - AssetName string `json:"assetName"` - AssetVersion string `json:"assetVersion"` - Sbom json.RawMessage `json:"sbom,omitempty"` + Verb string `json:"verb"` + ProjectExternalEntityID string `json:"projectExternalEntityId"` + ProjectName string `json:"projectName"` + ProjectDescription string `json:"projectDescription"` + SubProjectExternalEntityID string `json:"subProjectExternalEntityId,omitempty"` + SubProjectName string `json:"subProjectName,omitempty"` + SubProjectDescription string `json:"subProjectDescription,omitempty"` + AssetExternalEntityID string `json:"assetExternalEntityId"` + AssetName string `json:"assetName"` + AssetDescription string `json:"assetDescription"` + AssetVersionName string `json:"assetVersionName"` + Artifact string `json:"artifact"` + Sbom json.RawMessage `json:"sbom,omitempty"` } diff --git a/mocks/mock_ArtifactRepository.go b/mocks/mock_ArtifactRepository.go index 4f47130fe..910da19f4 100644 --- a/mocks/mock_ArtifactRepository.go +++ b/mocks/mock_ArtifactRepository.go @@ -677,6 +677,80 @@ func (_c *ArtifactRepository_GetAllArtifactAffectedByDependencyVuln_Call) RunAnd return _c } +// GetByAssetID provides a mock function for the type ArtifactRepository +func (_mock *ArtifactRepository) GetByAssetID(ctx context.Context, tx shared.DB, assetID uuid.UUID) ([]models.Artifact, error) { + ret := _mock.Called(ctx, tx, assetID) + + if len(ret) == 0 { + panic("no return value specified for GetByAssetID") + } + + var r0 []models.Artifact + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, shared.DB, uuid.UUID) ([]models.Artifact, error)); ok { + return returnFunc(ctx, tx, assetID) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, shared.DB, uuid.UUID) []models.Artifact); ok { + r0 = returnFunc(ctx, tx, assetID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]models.Artifact) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, shared.DB, uuid.UUID) error); ok { + r1 = returnFunc(ctx, tx, assetID) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// ArtifactRepository_GetByAssetID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetByAssetID' +type ArtifactRepository_GetByAssetID_Call struct { + *mock.Call +} + +// GetByAssetID is a helper method to define mock.On call +// - ctx context.Context +// - tx shared.DB +// - assetID uuid.UUID +func (_e *ArtifactRepository_Expecter) GetByAssetID(ctx interface{}, tx interface{}, assetID interface{}) *ArtifactRepository_GetByAssetID_Call { + return &ArtifactRepository_GetByAssetID_Call{Call: _e.mock.On("GetByAssetID", ctx, tx, assetID)} +} + +func (_c *ArtifactRepository_GetByAssetID_Call) Run(run func(ctx context.Context, tx shared.DB, assetID uuid.UUID)) *ArtifactRepository_GetByAssetID_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 shared.DB + if args[1] != nil { + arg1 = args[1].(shared.DB) + } + var arg2 uuid.UUID + if args[2] != nil { + arg2 = args[2].(uuid.UUID) + } + run( + arg0, + arg1, + arg2, + ) + }) + return _c +} + +func (_c *ArtifactRepository_GetByAssetID_Call) Return(artifacts []models.Artifact, err error) *ArtifactRepository_GetByAssetID_Call { + _c.Call.Return(artifacts, err) + return _c +} + +func (_c *ArtifactRepository_GetByAssetID_Call) RunAndReturn(run func(ctx context.Context, tx shared.DB, assetID uuid.UUID) ([]models.Artifact, error)) *ArtifactRepository_GetByAssetID_Call { + _c.Call.Return(run) + return _c +} + // GetByAssetIDAndAssetVersionName provides a mock function for the type ArtifactRepository func (_mock *ArtifactRepository) GetByAssetIDAndAssetVersionName(ctx context.Context, tx shared.DB, assetID uuid.UUID, assetVersionName string) ([]models.Artifact, error) { ret := _mock.Called(ctx, tx, assetID, assetVersionName) @@ -757,6 +831,80 @@ func (_c *ArtifactRepository_GetByAssetIDAndAssetVersionName_Call) RunAndReturn( return _c } +// GetByAssetIDs provides a mock function for the type ArtifactRepository +func (_mock *ArtifactRepository) GetByAssetIDs(ctx context.Context, tx shared.DB, assetIDs []uuid.UUID) ([]models.Artifact, error) { + ret := _mock.Called(ctx, tx, assetIDs) + + if len(ret) == 0 { + panic("no return value specified for GetByAssetIDs") + } + + var r0 []models.Artifact + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, shared.DB, []uuid.UUID) ([]models.Artifact, error)); ok { + return returnFunc(ctx, tx, assetIDs) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, shared.DB, []uuid.UUID) []models.Artifact); ok { + r0 = returnFunc(ctx, tx, assetIDs) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]models.Artifact) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, shared.DB, []uuid.UUID) error); ok { + r1 = returnFunc(ctx, tx, assetIDs) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// ArtifactRepository_GetByAssetIDs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetByAssetIDs' +type ArtifactRepository_GetByAssetIDs_Call struct { + *mock.Call +} + +// GetByAssetIDs is a helper method to define mock.On call +// - ctx context.Context +// - tx shared.DB +// - assetIDs []uuid.UUID +func (_e *ArtifactRepository_Expecter) GetByAssetIDs(ctx interface{}, tx interface{}, assetIDs interface{}) *ArtifactRepository_GetByAssetIDs_Call { + return &ArtifactRepository_GetByAssetIDs_Call{Call: _e.mock.On("GetByAssetIDs", ctx, tx, assetIDs)} +} + +func (_c *ArtifactRepository_GetByAssetIDs_Call) Run(run func(ctx context.Context, tx shared.DB, assetIDs []uuid.UUID)) *ArtifactRepository_GetByAssetIDs_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 shared.DB + if args[1] != nil { + arg1 = args[1].(shared.DB) + } + var arg2 []uuid.UUID + if args[2] != nil { + arg2 = args[2].([]uuid.UUID) + } + run( + arg0, + arg1, + arg2, + ) + }) + return _c +} + +func (_c *ArtifactRepository_GetByAssetIDs_Call) Return(artifacts []models.Artifact, err error) *ArtifactRepository_GetByAssetIDs_Call { + _c.Call.Return(artifacts, err) + return _c +} + +func (_c *ArtifactRepository_GetByAssetIDs_Call) RunAndReturn(run func(ctx context.Context, tx shared.DB, assetIDs []uuid.UUID) ([]models.Artifact, error)) *ArtifactRepository_GetByAssetIDs_Call { + _c.Call.Return(run) + return _c +} + // GetByAssetVersions provides a mock function for the type ArtifactRepository func (_mock *ArtifactRepository) GetByAssetVersions(ctx context.Context, tx shared.DB, assetID uuid.UUID, assetVersionNames []string) ([]models.Artifact, error) { ret := _mock.Called(ctx, tx, assetID, assetVersionNames) diff --git a/mocks/mock_AssetRepository.go b/mocks/mock_AssetRepository.go index 27036fe34..bd3b8f488 100644 --- a/mocks/mock_AssetRepository.go +++ b/mocks/mock_AssetRepository.go @@ -1130,6 +1130,160 @@ func (_c *AssetRepository_GetByProjectID_Call) RunAndReturn(run func(ctx context return _c } +// GetByProjectIDs provides a mock function for the type AssetRepository +func (_mock *AssetRepository) GetByProjectIDs(ctx context.Context, tx shared.DB, projectIDs []uuid.UUID) ([]models.Asset, error) { + ret := _mock.Called(ctx, tx, projectIDs) + + if len(ret) == 0 { + panic("no return value specified for GetByProjectIDs") + } + + var r0 []models.Asset + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, shared.DB, []uuid.UUID) ([]models.Asset, error)); ok { + return returnFunc(ctx, tx, projectIDs) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, shared.DB, []uuid.UUID) []models.Asset); ok { + r0 = returnFunc(ctx, tx, projectIDs) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]models.Asset) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, shared.DB, []uuid.UUID) error); ok { + r1 = returnFunc(ctx, tx, projectIDs) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// AssetRepository_GetByProjectIDs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetByProjectIDs' +type AssetRepository_GetByProjectIDs_Call struct { + *mock.Call +} + +// GetByProjectIDs is a helper method to define mock.On call +// - ctx context.Context +// - tx shared.DB +// - projectIDs []uuid.UUID +func (_e *AssetRepository_Expecter) GetByProjectIDs(ctx interface{}, tx interface{}, projectIDs interface{}) *AssetRepository_GetByProjectIDs_Call { + return &AssetRepository_GetByProjectIDs_Call{Call: _e.mock.On("GetByProjectIDs", ctx, tx, projectIDs)} +} + +func (_c *AssetRepository_GetByProjectIDs_Call) Run(run func(ctx context.Context, tx shared.DB, projectIDs []uuid.UUID)) *AssetRepository_GetByProjectIDs_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 shared.DB + if args[1] != nil { + arg1 = args[1].(shared.DB) + } + var arg2 []uuid.UUID + if args[2] != nil { + arg2 = args[2].([]uuid.UUID) + } + run( + arg0, + arg1, + arg2, + ) + }) + return _c +} + +func (_c *AssetRepository_GetByProjectIDs_Call) Return(assets []models.Asset, err error) *AssetRepository_GetByProjectIDs_Call { + _c.Call.Return(assets, err) + return _c +} + +func (_c *AssetRepository_GetByProjectIDs_Call) RunAndReturn(run func(ctx context.Context, tx shared.DB, projectIDs []uuid.UUID) ([]models.Asset, error)) *AssetRepository_GetByProjectIDs_Call { + _c.Call.Return(run) + return _c +} + +// GetByProjectIDsWithProviderID provides a mock function for the type AssetRepository +func (_mock *AssetRepository) GetByProjectIDsWithProviderID(ctx context.Context, tx shared.DB, projectIDs []uuid.UUID, providerID string) ([]models.Asset, error) { + ret := _mock.Called(ctx, tx, projectIDs, providerID) + + if len(ret) == 0 { + panic("no return value specified for GetByProjectIDsWithProviderID") + } + + var r0 []models.Asset + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, shared.DB, []uuid.UUID, string) ([]models.Asset, error)); ok { + return returnFunc(ctx, tx, projectIDs, providerID) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, shared.DB, []uuid.UUID, string) []models.Asset); ok { + r0 = returnFunc(ctx, tx, projectIDs, providerID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]models.Asset) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, shared.DB, []uuid.UUID, string) error); ok { + r1 = returnFunc(ctx, tx, projectIDs, providerID) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// AssetRepository_GetByProjectIDsWithProviderID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetByProjectIDsWithProviderID' +type AssetRepository_GetByProjectIDsWithProviderID_Call struct { + *mock.Call +} + +// GetByProjectIDsWithProviderID is a helper method to define mock.On call +// - ctx context.Context +// - tx shared.DB +// - projectIDs []uuid.UUID +// - providerID string +func (_e *AssetRepository_Expecter) GetByProjectIDsWithProviderID(ctx interface{}, tx interface{}, projectIDs interface{}, providerID interface{}) *AssetRepository_GetByProjectIDsWithProviderID_Call { + return &AssetRepository_GetByProjectIDsWithProviderID_Call{Call: _e.mock.On("GetByProjectIDsWithProviderID", ctx, tx, projectIDs, providerID)} +} + +func (_c *AssetRepository_GetByProjectIDsWithProviderID_Call) Run(run func(ctx context.Context, tx shared.DB, projectIDs []uuid.UUID, providerID string)) *AssetRepository_GetByProjectIDsWithProviderID_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 shared.DB + if args[1] != nil { + arg1 = args[1].(shared.DB) + } + var arg2 []uuid.UUID + if args[2] != nil { + arg2 = args[2].([]uuid.UUID) + } + var arg3 string + if args[3] != nil { + arg3 = args[3].(string) + } + run( + arg0, + arg1, + arg2, + arg3, + ) + }) + return _c +} + +func (_c *AssetRepository_GetByProjectIDsWithProviderID_Call) Return(assets []models.Asset, err error) *AssetRepository_GetByProjectIDsWithProviderID_Call { + _c.Call.Return(assets, err) + return _c +} + +func (_c *AssetRepository_GetByProjectIDsWithProviderID_Call) RunAndReturn(run func(ctx context.Context, tx shared.DB, projectIDs []uuid.UUID, providerID string) ([]models.Asset, error)) *AssetRepository_GetByProjectIDsWithProviderID_Call { + _c.Call.Return(run) + return _c +} + // GetDB provides a mock function for the type AssetRepository func (_mock *AssetRepository) GetDB(ctx context.Context, tx shared.DB) shared.DB { ret := _mock.Called(ctx, tx) diff --git a/mocks/mock_AssetService.go b/mocks/mock_AssetService.go index 5634b418a..7256e2630 100644 --- a/mocks/mock_AssetService.go +++ b/mocks/mock_AssetService.go @@ -184,8 +184,8 @@ func (_c *AssetService_CreateAsset_Call) RunAndReturn(run func(ctx context.Conte } // FindOrCreateAsset provides a mock function for the type AssetService -func (_mock *AssetService) FindOrCreateAsset(ctx context.Context, rbac shared.AccessControl, providerID string, orgID uuid.UUID, projectID uuid.UUID, name string, externalEntityID string, currentUser string) (*models.Asset, error) { - ret := _mock.Called(ctx, rbac, providerID, orgID, projectID, name, externalEntityID, currentUser) +func (_mock *AssetService) FindOrCreateAsset(ctx context.Context, rbac shared.AccessControl, providerID string, orgID uuid.UUID, projectID uuid.UUID, name string, externalEntityID string, currentUser string, description string) (*models.Asset, error) { + ret := _mock.Called(ctx, rbac, providerID, orgID, projectID, name, externalEntityID, currentUser, description) if len(ret) == 0 { panic("no return value specified for FindOrCreateAsset") @@ -193,18 +193,18 @@ func (_mock *AssetService) FindOrCreateAsset(ctx context.Context, rbac shared.Ac var r0 *models.Asset var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, shared.AccessControl, string, uuid.UUID, uuid.UUID, string, string, string) (*models.Asset, error)); ok { - return returnFunc(ctx, rbac, providerID, orgID, projectID, name, externalEntityID, currentUser) + if returnFunc, ok := ret.Get(0).(func(context.Context, shared.AccessControl, string, uuid.UUID, uuid.UUID, string, string, string, string) (*models.Asset, error)); ok { + return returnFunc(ctx, rbac, providerID, orgID, projectID, name, externalEntityID, currentUser, description) } - if returnFunc, ok := ret.Get(0).(func(context.Context, shared.AccessControl, string, uuid.UUID, uuid.UUID, string, string, string) *models.Asset); ok { - r0 = returnFunc(ctx, rbac, providerID, orgID, projectID, name, externalEntityID, currentUser) + if returnFunc, ok := ret.Get(0).(func(context.Context, shared.AccessControl, string, uuid.UUID, uuid.UUID, string, string, string, string) *models.Asset); ok { + r0 = returnFunc(ctx, rbac, providerID, orgID, projectID, name, externalEntityID, currentUser, description) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*models.Asset) } } - if returnFunc, ok := ret.Get(1).(func(context.Context, shared.AccessControl, string, uuid.UUID, uuid.UUID, string, string, string) error); ok { - r1 = returnFunc(ctx, rbac, providerID, orgID, projectID, name, externalEntityID, currentUser) + if returnFunc, ok := ret.Get(1).(func(context.Context, shared.AccessControl, string, uuid.UUID, uuid.UUID, string, string, string, string) error); ok { + r1 = returnFunc(ctx, rbac, providerID, orgID, projectID, name, externalEntityID, currentUser, description) } else { r1 = ret.Error(1) } @@ -225,11 +225,12 @@ type AssetService_FindOrCreateAsset_Call struct { // - name string // - externalEntityID string // - currentUser string -func (_e *AssetService_Expecter) FindOrCreateAsset(ctx interface{}, rbac interface{}, providerID interface{}, orgID interface{}, projectID interface{}, name interface{}, externalEntityID interface{}, currentUser interface{}) *AssetService_FindOrCreateAsset_Call { - return &AssetService_FindOrCreateAsset_Call{Call: _e.mock.On("FindOrCreateAsset", ctx, rbac, providerID, orgID, projectID, name, externalEntityID, currentUser)} +// - description string +func (_e *AssetService_Expecter) FindOrCreateAsset(ctx interface{}, rbac interface{}, providerID interface{}, orgID interface{}, projectID interface{}, name interface{}, externalEntityID interface{}, currentUser interface{}, description interface{}) *AssetService_FindOrCreateAsset_Call { + return &AssetService_FindOrCreateAsset_Call{Call: _e.mock.On("FindOrCreateAsset", ctx, rbac, providerID, orgID, projectID, name, externalEntityID, currentUser, description)} } -func (_c *AssetService_FindOrCreateAsset_Call) Run(run func(ctx context.Context, rbac shared.AccessControl, providerID string, orgID uuid.UUID, projectID uuid.UUID, name string, externalEntityID string, currentUser string)) *AssetService_FindOrCreateAsset_Call { +func (_c *AssetService_FindOrCreateAsset_Call) Run(run func(ctx context.Context, rbac shared.AccessControl, providerID string, orgID uuid.UUID, projectID uuid.UUID, name string, externalEntityID string, currentUser string, description string)) *AssetService_FindOrCreateAsset_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -263,6 +264,10 @@ func (_c *AssetService_FindOrCreateAsset_Call) Run(run func(ctx context.Context, if args[7] != nil { arg7 = args[7].(string) } + var arg8 string + if args[8] != nil { + arg8 = args[8].(string) + } run( arg0, arg1, @@ -272,6 +277,7 @@ func (_c *AssetService_FindOrCreateAsset_Call) Run(run func(ctx context.Context, arg5, arg6, arg7, + arg8, ) }) return _c @@ -282,7 +288,7 @@ func (_c *AssetService_FindOrCreateAsset_Call) Return(asset *models.Asset, err e return _c } -func (_c *AssetService_FindOrCreateAsset_Call) RunAndReturn(run func(ctx context.Context, rbac shared.AccessControl, providerID string, orgID uuid.UUID, projectID uuid.UUID, name string, externalEntityID string, currentUser string) (*models.Asset, error)) *AssetService_FindOrCreateAsset_Call { +func (_c *AssetService_FindOrCreateAsset_Call) RunAndReturn(run func(ctx context.Context, rbac shared.AccessControl, providerID string, orgID uuid.UUID, projectID uuid.UUID, name string, externalEntityID string, currentUser string, description string) (*models.Asset, error)) *AssetService_FindOrCreateAsset_Call { _c.Call.Return(run) return _c } diff --git a/mocks/mock_AssetVersionRepository.go b/mocks/mock_AssetVersionRepository.go index 953be143d..44edbfd3e 100644 --- a/mocks/mock_AssetVersionRepository.go +++ b/mocks/mock_AssetVersionRepository.go @@ -758,6 +758,80 @@ func (_c *AssetVersionRepository_GetAssetVersionsByAssetIDWithArtifacts_Call) Ru return _c } +// GetAssetVersionsByAssetIDs provides a mock function for the type AssetVersionRepository +func (_mock *AssetVersionRepository) GetAssetVersionsByAssetIDs(ctx context.Context, tx shared.DB, assetIDs []uuid.UUID) ([]models.AssetVersion, error) { + ret := _mock.Called(ctx, tx, assetIDs) + + if len(ret) == 0 { + panic("no return value specified for GetAssetVersionsByAssetIDs") + } + + var r0 []models.AssetVersion + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, shared.DB, []uuid.UUID) ([]models.AssetVersion, error)); ok { + return returnFunc(ctx, tx, assetIDs) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, shared.DB, []uuid.UUID) []models.AssetVersion); ok { + r0 = returnFunc(ctx, tx, assetIDs) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]models.AssetVersion) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, shared.DB, []uuid.UUID) error); ok { + r1 = returnFunc(ctx, tx, assetIDs) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// AssetVersionRepository_GetAssetVersionsByAssetIDs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAssetVersionsByAssetIDs' +type AssetVersionRepository_GetAssetVersionsByAssetIDs_Call struct { + *mock.Call +} + +// GetAssetVersionsByAssetIDs is a helper method to define mock.On call +// - ctx context.Context +// - tx shared.DB +// - assetIDs []uuid.UUID +func (_e *AssetVersionRepository_Expecter) GetAssetVersionsByAssetIDs(ctx interface{}, tx interface{}, assetIDs interface{}) *AssetVersionRepository_GetAssetVersionsByAssetIDs_Call { + return &AssetVersionRepository_GetAssetVersionsByAssetIDs_Call{Call: _e.mock.On("GetAssetVersionsByAssetIDs", ctx, tx, assetIDs)} +} + +func (_c *AssetVersionRepository_GetAssetVersionsByAssetIDs_Call) Run(run func(ctx context.Context, tx shared.DB, assetIDs []uuid.UUID)) *AssetVersionRepository_GetAssetVersionsByAssetIDs_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 shared.DB + if args[1] != nil { + arg1 = args[1].(shared.DB) + } + var arg2 []uuid.UUID + if args[2] != nil { + arg2 = args[2].([]uuid.UUID) + } + run( + arg0, + arg1, + arg2, + ) + }) + return _c +} + +func (_c *AssetVersionRepository_GetAssetVersionsByAssetIDs_Call) Return(assetVersions []models.AssetVersion, err error) *AssetVersionRepository_GetAssetVersionsByAssetIDs_Call { + _c.Call.Return(assetVersions, err) + return _c +} + +func (_c *AssetVersionRepository_GetAssetVersionsByAssetIDs_Call) RunAndReturn(run func(ctx context.Context, tx shared.DB, assetIDs []uuid.UUID) ([]models.AssetVersion, error)) *AssetVersionRepository_GetAssetVersionsByAssetIDs_Call { + _c.Call.Return(run) + return _c +} + // GetDB provides a mock function for the type AssetVersionRepository func (_mock *AssetVersionRepository) GetDB(ctx context.Context, tx shared.DB) shared.DB { ret := _mock.Called(ctx, tx) diff --git a/mocks/mock_ProjectRepository.go b/mocks/mock_ProjectRepository.go index 5b7f6cb78..ae52ca988 100644 --- a/mocks/mock_ProjectRepository.go +++ b/mocks/mock_ProjectRepository.go @@ -174,16 +174,16 @@ func (_c *ProjectRepository_All_Call) RunAndReturn(run func(ctx context.Context, } // CleanupDynamicProject provides a mock function for the type ProjectRepository -func (_mock *ProjectRepository) CleanupDynamicProject(ctx context.Context, tx shared.DB, organizationID uuid.UUID, parentProjectID uuid.UUID, providerID string, projectExternalEntityID string, assetExternalEntityID string, assetVersionName string) error { - ret := _mock.Called(ctx, tx, organizationID, parentProjectID, providerID, projectExternalEntityID, assetExternalEntityID, assetVersionName) +func (_mock *ProjectRepository) CleanupDynamicProject(ctx context.Context, tx shared.DB, organizationID uuid.UUID, parentProjectID uuid.UUID, providerID string, projectExternalEntityID string, assetExternalEntityID string, assetVersionName string, artifactName string) error { + ret := _mock.Called(ctx, tx, organizationID, parentProjectID, providerID, projectExternalEntityID, assetExternalEntityID, assetVersionName, artifactName) if len(ret) == 0 { panic("no return value specified for CleanupDynamicProject") } var r0 error - if returnFunc, ok := ret.Get(0).(func(context.Context, shared.DB, uuid.UUID, uuid.UUID, string, string, string, string) error); ok { - r0 = returnFunc(ctx, tx, organizationID, parentProjectID, providerID, projectExternalEntityID, assetExternalEntityID, assetVersionName) + if returnFunc, ok := ret.Get(0).(func(context.Context, shared.DB, uuid.UUID, uuid.UUID, string, string, string, string, string) error); ok { + r0 = returnFunc(ctx, tx, organizationID, parentProjectID, providerID, projectExternalEntityID, assetExternalEntityID, assetVersionName, artifactName) } else { r0 = ret.Error(0) } @@ -204,11 +204,12 @@ type ProjectRepository_CleanupDynamicProject_Call struct { // - projectExternalEntityID string // - assetExternalEntityID string // - assetVersionName string -func (_e *ProjectRepository_Expecter) CleanupDynamicProject(ctx interface{}, tx interface{}, organizationID interface{}, parentProjectID interface{}, providerID interface{}, projectExternalEntityID interface{}, assetExternalEntityID interface{}, assetVersionName interface{}) *ProjectRepository_CleanupDynamicProject_Call { - return &ProjectRepository_CleanupDynamicProject_Call{Call: _e.mock.On("CleanupDynamicProject", ctx, tx, organizationID, parentProjectID, providerID, projectExternalEntityID, assetExternalEntityID, assetVersionName)} +// - artifactName string +func (_e *ProjectRepository_Expecter) CleanupDynamicProject(ctx interface{}, tx interface{}, organizationID interface{}, parentProjectID interface{}, providerID interface{}, projectExternalEntityID interface{}, assetExternalEntityID interface{}, assetVersionName interface{}, artifactName interface{}) *ProjectRepository_CleanupDynamicProject_Call { + return &ProjectRepository_CleanupDynamicProject_Call{Call: _e.mock.On("CleanupDynamicProject", ctx, tx, organizationID, parentProjectID, providerID, projectExternalEntityID, assetExternalEntityID, assetVersionName, artifactName)} } -func (_c *ProjectRepository_CleanupDynamicProject_Call) Run(run func(ctx context.Context, tx shared.DB, organizationID uuid.UUID, parentProjectID uuid.UUID, providerID string, projectExternalEntityID string, assetExternalEntityID string, assetVersionName string)) *ProjectRepository_CleanupDynamicProject_Call { +func (_c *ProjectRepository_CleanupDynamicProject_Call) Run(run func(ctx context.Context, tx shared.DB, organizationID uuid.UUID, parentProjectID uuid.UUID, providerID string, projectExternalEntityID string, assetExternalEntityID string, assetVersionName string, artifactName string)) *ProjectRepository_CleanupDynamicProject_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -242,6 +243,10 @@ func (_c *ProjectRepository_CleanupDynamicProject_Call) Run(run func(ctx context if args[7] != nil { arg7 = args[7].(string) } + var arg8 string + if args[8] != nil { + arg8 = args[8].(string) + } run( arg0, arg1, @@ -251,6 +256,7 @@ func (_c *ProjectRepository_CleanupDynamicProject_Call) Run(run func(ctx context arg5, arg6, arg7, + arg8, ) }) return _c @@ -261,7 +267,7 @@ func (_c *ProjectRepository_CleanupDynamicProject_Call) Return(err error) *Proje return _c } -func (_c *ProjectRepository_CleanupDynamicProject_Call) RunAndReturn(run func(ctx context.Context, tx shared.DB, organizationID uuid.UUID, parentProjectID uuid.UUID, providerID string, projectExternalEntityID string, assetExternalEntityID string, assetVersionName string) error) *ProjectRepository_CleanupDynamicProject_Call { +func (_c *ProjectRepository_CleanupDynamicProject_Call) RunAndReturn(run func(ctx context.Context, tx shared.DB, organizationID uuid.UUID, parentProjectID uuid.UUID, providerID string, projectExternalEntityID string, assetExternalEntityID string, assetVersionName string, artifactName string) error) *ProjectRepository_CleanupDynamicProject_Call { _c.Call.Return(run) return _c } @@ -741,6 +747,86 @@ func (_c *ProjectRepository_GetByProjectIDs_Call) RunAndReturn(run func(ctx cont return _c } +// GetChildProjectsForParents provides a mock function for the type ProjectRepository +func (_mock *ProjectRepository) GetChildProjectsForParents(ctx context.Context, tx shared.DB, parentIDs []uuid.UUID, providerID string) ([]models.Project, error) { + ret := _mock.Called(ctx, tx, parentIDs, providerID) + + if len(ret) == 0 { + panic("no return value specified for GetChildProjectsForParents") + } + + var r0 []models.Project + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, shared.DB, []uuid.UUID, string) ([]models.Project, error)); ok { + return returnFunc(ctx, tx, parentIDs, providerID) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, shared.DB, []uuid.UUID, string) []models.Project); ok { + r0 = returnFunc(ctx, tx, parentIDs, providerID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]models.Project) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, shared.DB, []uuid.UUID, string) error); ok { + r1 = returnFunc(ctx, tx, parentIDs, providerID) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// ProjectRepository_GetChildProjectsForParents_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetChildProjectsForParents' +type ProjectRepository_GetChildProjectsForParents_Call struct { + *mock.Call +} + +// GetChildProjectsForParents is a helper method to define mock.On call +// - ctx context.Context +// - tx shared.DB +// - parentIDs []uuid.UUID +// - providerID string +func (_e *ProjectRepository_Expecter) GetChildProjectsForParents(ctx interface{}, tx interface{}, parentIDs interface{}, providerID interface{}) *ProjectRepository_GetChildProjectsForParents_Call { + return &ProjectRepository_GetChildProjectsForParents_Call{Call: _e.mock.On("GetChildProjectsForParents", ctx, tx, parentIDs, providerID)} +} + +func (_c *ProjectRepository_GetChildProjectsForParents_Call) Run(run func(ctx context.Context, tx shared.DB, parentIDs []uuid.UUID, providerID string)) *ProjectRepository_GetChildProjectsForParents_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 shared.DB + if args[1] != nil { + arg1 = args[1].(shared.DB) + } + var arg2 []uuid.UUID + if args[2] != nil { + arg2 = args[2].([]uuid.UUID) + } + var arg3 string + if args[3] != nil { + arg3 = args[3].(string) + } + run( + arg0, + arg1, + arg2, + arg3, + ) + }) + return _c +} + +func (_c *ProjectRepository_GetChildProjectsForParents_Call) Return(projects []models.Project, err error) *ProjectRepository_GetChildProjectsForParents_Call { + _c.Call.Return(projects, err) + return _c +} + +func (_c *ProjectRepository_GetChildProjectsForParents_Call) RunAndReturn(run func(ctx context.Context, tx shared.DB, parentIDs []uuid.UUID, providerID string) ([]models.Project, error)) *ProjectRepository_GetChildProjectsForParents_Call { + _c.Call.Return(run) + return _c +} + // GetDirectChildProjects provides a mock function for the type ProjectRepository func (_mock *ProjectRepository) GetDirectChildProjects(ctx context.Context, tx shared.DB, projectID uuid.UUID) ([]models.Project, error) { ret := _mock.Called(ctx, tx, projectID) @@ -815,6 +901,170 @@ func (_c *ProjectRepository_GetDirectChildProjects_Call) RunAndReturn(run func(c return _c } +// GetDirectChildProjectsWithProviderID provides a mock function for the type ProjectRepository +func (_mock *ProjectRepository) GetDirectChildProjectsWithProviderID(ctx context.Context, tx shared.DB, parentID uuid.UUID, providerID string) ([]models.Project, error) { + ret := _mock.Called(ctx, tx, parentID, providerID) + + if len(ret) == 0 { + panic("no return value specified for GetDirectChildProjectsWithProviderID") + } + + var r0 []models.Project + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, shared.DB, uuid.UUID, string) ([]models.Project, error)); ok { + return returnFunc(ctx, tx, parentID, providerID) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, shared.DB, uuid.UUID, string) []models.Project); ok { + r0 = returnFunc(ctx, tx, parentID, providerID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]models.Project) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, shared.DB, uuid.UUID, string) error); ok { + r1 = returnFunc(ctx, tx, parentID, providerID) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// ProjectRepository_GetDirectChildProjectsWithProviderID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetDirectChildProjectsWithProviderID' +type ProjectRepository_GetDirectChildProjectsWithProviderID_Call struct { + *mock.Call +} + +// GetDirectChildProjectsWithProviderID is a helper method to define mock.On call +// - ctx context.Context +// - tx shared.DB +// - parentID uuid.UUID +// - providerID string +func (_e *ProjectRepository_Expecter) GetDirectChildProjectsWithProviderID(ctx interface{}, tx interface{}, parentID interface{}, providerID interface{}) *ProjectRepository_GetDirectChildProjectsWithProviderID_Call { + return &ProjectRepository_GetDirectChildProjectsWithProviderID_Call{Call: _e.mock.On("GetDirectChildProjectsWithProviderID", ctx, tx, parentID, providerID)} +} + +func (_c *ProjectRepository_GetDirectChildProjectsWithProviderID_Call) Run(run func(ctx context.Context, tx shared.DB, parentID uuid.UUID, providerID string)) *ProjectRepository_GetDirectChildProjectsWithProviderID_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 shared.DB + if args[1] != nil { + arg1 = args[1].(shared.DB) + } + var arg2 uuid.UUID + if args[2] != nil { + arg2 = args[2].(uuid.UUID) + } + var arg3 string + if args[3] != nil { + arg3 = args[3].(string) + } + run( + arg0, + arg1, + arg2, + arg3, + ) + }) + return _c +} + +func (_c *ProjectRepository_GetDirectChildProjectsWithProviderID_Call) Return(projects []models.Project, err error) *ProjectRepository_GetDirectChildProjectsWithProviderID_Call { + _c.Call.Return(projects, err) + return _c +} + +func (_c *ProjectRepository_GetDirectChildProjectsWithProviderID_Call) RunAndReturn(run func(ctx context.Context, tx shared.DB, parentID uuid.UUID, providerID string) ([]models.Project, error)) *ProjectRepository_GetDirectChildProjectsWithProviderID_Call { + _c.Call.Return(run) + return _c +} + +// GetDirectChildProjectsWithProviderIDAndExternalEntityID provides a mock function for the type ProjectRepository +func (_mock *ProjectRepository) GetDirectChildProjectsWithProviderIDAndExternalEntityID(ctx context.Context, tx shared.DB, parentID uuid.UUID, providerID string, externalEntityID string) (models.Project, error) { + ret := _mock.Called(ctx, tx, parentID, providerID, externalEntityID) + + if len(ret) == 0 { + panic("no return value specified for GetDirectChildProjectsWithProviderIDAndExternalEntityID") + } + + var r0 models.Project + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, shared.DB, uuid.UUID, string, string) (models.Project, error)); ok { + return returnFunc(ctx, tx, parentID, providerID, externalEntityID) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, shared.DB, uuid.UUID, string, string) models.Project); ok { + r0 = returnFunc(ctx, tx, parentID, providerID, externalEntityID) + } else { + r0 = ret.Get(0).(models.Project) + } + if returnFunc, ok := ret.Get(1).(func(context.Context, shared.DB, uuid.UUID, string, string) error); ok { + r1 = returnFunc(ctx, tx, parentID, providerID, externalEntityID) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// ProjectRepository_GetDirectChildProjectsWithProviderIDAndExternalEntityID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetDirectChildProjectsWithProviderIDAndExternalEntityID' +type ProjectRepository_GetDirectChildProjectsWithProviderIDAndExternalEntityID_Call struct { + *mock.Call +} + +// GetDirectChildProjectsWithProviderIDAndExternalEntityID is a helper method to define mock.On call +// - ctx context.Context +// - tx shared.DB +// - parentID uuid.UUID +// - providerID string +// - externalEntityID string +func (_e *ProjectRepository_Expecter) GetDirectChildProjectsWithProviderIDAndExternalEntityID(ctx interface{}, tx interface{}, parentID interface{}, providerID interface{}, externalEntityID interface{}) *ProjectRepository_GetDirectChildProjectsWithProviderIDAndExternalEntityID_Call { + return &ProjectRepository_GetDirectChildProjectsWithProviderIDAndExternalEntityID_Call{Call: _e.mock.On("GetDirectChildProjectsWithProviderIDAndExternalEntityID", ctx, tx, parentID, providerID, externalEntityID)} +} + +func (_c *ProjectRepository_GetDirectChildProjectsWithProviderIDAndExternalEntityID_Call) Run(run func(ctx context.Context, tx shared.DB, parentID uuid.UUID, providerID string, externalEntityID string)) *ProjectRepository_GetDirectChildProjectsWithProviderIDAndExternalEntityID_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 shared.DB + if args[1] != nil { + arg1 = args[1].(shared.DB) + } + var arg2 uuid.UUID + if args[2] != nil { + arg2 = args[2].(uuid.UUID) + } + var arg3 string + if args[3] != nil { + arg3 = args[3].(string) + } + var arg4 string + if args[4] != nil { + arg4 = args[4].(string) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + ) + }) + return _c +} + +func (_c *ProjectRepository_GetDirectChildProjectsWithProviderIDAndExternalEntityID_Call) Return(project models.Project, err error) *ProjectRepository_GetDirectChildProjectsWithProviderIDAndExternalEntityID_Call { + _c.Call.Return(project, err) + return _c +} + +func (_c *ProjectRepository_GetDirectChildProjectsWithProviderIDAndExternalEntityID_Call) RunAndReturn(run func(ctx context.Context, tx shared.DB, parentID uuid.UUID, providerID string, externalEntityID string) (models.Project, error)) *ProjectRepository_GetDirectChildProjectsWithProviderIDAndExternalEntityID_Call { + _c.Call.Return(run) + return _c +} + // GetProjectByAssetID provides a mock function for the type ProjectRepository func (_mock *ProjectRepository) GetProjectByAssetID(ctx context.Context, tx shared.DB, assetID uuid.UUID) (models.Project, error) { ret := _mock.Called(ctx, tx, assetID) diff --git a/mocks/mock_ProjectService.go b/mocks/mock_ProjectService.go index 59f53c1ba..35d010a8a 100644 --- a/mocks/mock_ProjectService.go +++ b/mocks/mock_ProjectService.go @@ -162,8 +162,8 @@ func (_c *ProjectService_CreateProject_Call) RunAndReturn(run func(ctx shared.Co } // FindOrCreateProject provides a mock function for the type ProjectService -func (_mock *ProjectService) FindOrCreateProject(ctx shared.Context, providerID string, orgID uuid.UUID, name string, externalEntityID string, parentID uuid.UUID) (*models.Project, error) { - ret := _mock.Called(ctx, providerID, orgID, name, externalEntityID, parentID) +func (_mock *ProjectService) FindOrCreateProject(ctx shared.Context, providerID string, orgID uuid.UUID, name string, externalEntityID string, parentID uuid.UUID, description string) (*models.Project, error) { + ret := _mock.Called(ctx, providerID, orgID, name, externalEntityID, parentID, description) if len(ret) == 0 { panic("no return value specified for FindOrCreateProject") @@ -171,18 +171,18 @@ func (_mock *ProjectService) FindOrCreateProject(ctx shared.Context, providerID var r0 *models.Project var r1 error - if returnFunc, ok := ret.Get(0).(func(shared.Context, string, uuid.UUID, string, string, uuid.UUID) (*models.Project, error)); ok { - return returnFunc(ctx, providerID, orgID, name, externalEntityID, parentID) + if returnFunc, ok := ret.Get(0).(func(shared.Context, string, uuid.UUID, string, string, uuid.UUID, string) (*models.Project, error)); ok { + return returnFunc(ctx, providerID, orgID, name, externalEntityID, parentID, description) } - if returnFunc, ok := ret.Get(0).(func(shared.Context, string, uuid.UUID, string, string, uuid.UUID) *models.Project); ok { - r0 = returnFunc(ctx, providerID, orgID, name, externalEntityID, parentID) + if returnFunc, ok := ret.Get(0).(func(shared.Context, string, uuid.UUID, string, string, uuid.UUID, string) *models.Project); ok { + r0 = returnFunc(ctx, providerID, orgID, name, externalEntityID, parentID, description) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*models.Project) } } - if returnFunc, ok := ret.Get(1).(func(shared.Context, string, uuid.UUID, string, string, uuid.UUID) error); ok { - r1 = returnFunc(ctx, providerID, orgID, name, externalEntityID, parentID) + if returnFunc, ok := ret.Get(1).(func(shared.Context, string, uuid.UUID, string, string, uuid.UUID, string) error); ok { + r1 = returnFunc(ctx, providerID, orgID, name, externalEntityID, parentID, description) } else { r1 = ret.Error(1) } @@ -201,11 +201,12 @@ type ProjectService_FindOrCreateProject_Call struct { // - name string // - externalEntityID string // - parentID uuid.UUID -func (_e *ProjectService_Expecter) FindOrCreateProject(ctx interface{}, providerID interface{}, orgID interface{}, name interface{}, externalEntityID interface{}, parentID interface{}) *ProjectService_FindOrCreateProject_Call { - return &ProjectService_FindOrCreateProject_Call{Call: _e.mock.On("FindOrCreateProject", ctx, providerID, orgID, name, externalEntityID, parentID)} +// - description string +func (_e *ProjectService_Expecter) FindOrCreateProject(ctx interface{}, providerID interface{}, orgID interface{}, name interface{}, externalEntityID interface{}, parentID interface{}, description interface{}) *ProjectService_FindOrCreateProject_Call { + return &ProjectService_FindOrCreateProject_Call{Call: _e.mock.On("FindOrCreateProject", ctx, providerID, orgID, name, externalEntityID, parentID, description)} } -func (_c *ProjectService_FindOrCreateProject_Call) Run(run func(ctx shared.Context, providerID string, orgID uuid.UUID, name string, externalEntityID string, parentID uuid.UUID)) *ProjectService_FindOrCreateProject_Call { +func (_c *ProjectService_FindOrCreateProject_Call) Run(run func(ctx shared.Context, providerID string, orgID uuid.UUID, name string, externalEntityID string, parentID uuid.UUID, description string)) *ProjectService_FindOrCreateProject_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 shared.Context if args[0] != nil { @@ -231,6 +232,10 @@ func (_c *ProjectService_FindOrCreateProject_Call) Run(run func(ctx shared.Conte if args[5] != nil { arg5 = args[5].(uuid.UUID) } + var arg6 string + if args[6] != nil { + arg6 = args[6].(string) + } run( arg0, arg1, @@ -238,6 +243,7 @@ func (_c *ProjectService_FindOrCreateProject_Call) Run(run func(ctx shared.Conte arg3, arg4, arg5, + arg6, ) }) return _c @@ -248,7 +254,7 @@ func (_c *ProjectService_FindOrCreateProject_Call) Return(project *models.Projec return _c } -func (_c *ProjectService_FindOrCreateProject_Call) RunAndReturn(run func(ctx shared.Context, providerID string, orgID uuid.UUID, name string, externalEntityID string, parentID uuid.UUID) (*models.Project, error)) *ProjectService_FindOrCreateProject_Call { +func (_c *ProjectService_FindOrCreateProject_Call) RunAndReturn(run func(ctx shared.Context, providerID string, orgID uuid.UUID, name string, externalEntityID string, parentID uuid.UUID, description string) (*models.Project, error)) *ProjectService_FindOrCreateProject_Call { _c.Call.Return(run) return _c } diff --git a/mocks/mock_ReleaseRepository.go b/mocks/mock_ReleaseRepository.go index c503b7ba2..10687553d 100644 --- a/mocks/mock_ReleaseRepository.go +++ b/mocks/mock_ReleaseRepository.go @@ -654,6 +654,84 @@ func (_c *ReleaseRepository_DeleteReleaseItem_Call) RunAndReturn(run func(ctx co return _c } +// FindOrCreate provides a mock function for the type ReleaseRepository +func (_mock *ReleaseRepository) FindOrCreate(ctx context.Context, tx shared.DB, projectID uuid.UUID, name string) (models.Release, error) { + ret := _mock.Called(ctx, tx, projectID, name) + + if len(ret) == 0 { + panic("no return value specified for FindOrCreate") + } + + var r0 models.Release + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, shared.DB, uuid.UUID, string) (models.Release, error)); ok { + return returnFunc(ctx, tx, projectID, name) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, shared.DB, uuid.UUID, string) models.Release); ok { + r0 = returnFunc(ctx, tx, projectID, name) + } else { + r0 = ret.Get(0).(models.Release) + } + if returnFunc, ok := ret.Get(1).(func(context.Context, shared.DB, uuid.UUID, string) error); ok { + r1 = returnFunc(ctx, tx, projectID, name) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// ReleaseRepository_FindOrCreate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindOrCreate' +type ReleaseRepository_FindOrCreate_Call struct { + *mock.Call +} + +// FindOrCreate is a helper method to define mock.On call +// - ctx context.Context +// - tx shared.DB +// - projectID uuid.UUID +// - name string +func (_e *ReleaseRepository_Expecter) FindOrCreate(ctx interface{}, tx interface{}, projectID interface{}, name interface{}) *ReleaseRepository_FindOrCreate_Call { + return &ReleaseRepository_FindOrCreate_Call{Call: _e.mock.On("FindOrCreate", ctx, tx, projectID, name)} +} + +func (_c *ReleaseRepository_FindOrCreate_Call) Run(run func(ctx context.Context, tx shared.DB, projectID uuid.UUID, name string)) *ReleaseRepository_FindOrCreate_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 shared.DB + if args[1] != nil { + arg1 = args[1].(shared.DB) + } + var arg2 uuid.UUID + if args[2] != nil { + arg2 = args[2].(uuid.UUID) + } + var arg3 string + if args[3] != nil { + arg3 = args[3].(string) + } + run( + arg0, + arg1, + arg2, + arg3, + ) + }) + return _c +} + +func (_c *ReleaseRepository_FindOrCreate_Call) Return(release models.Release, err error) *ReleaseRepository_FindOrCreate_Call { + _c.Call.Return(release, err) + return _c +} + +func (_c *ReleaseRepository_FindOrCreate_Call) RunAndReturn(run func(ctx context.Context, tx shared.DB, projectID uuid.UUID, name string) (models.Release, error)) *ReleaseRepository_FindOrCreate_Call { + _c.Call.Return(run) + return _c +} + // GetByProjectID provides a mock function for the type ReleaseRepository func (_mock *ReleaseRepository) GetByProjectID(ctx context.Context, tx shared.DB, projectID uuid.UUID) ([]models.Release, error) { ret := _mock.Called(ctx, tx, projectID) diff --git a/mocks/mock_ReleaseService.go b/mocks/mock_ReleaseService.go index 237caf1cb..99abcd3f8 100644 --- a/mocks/mock_ReleaseService.go +++ b/mocks/mock_ReleaseService.go @@ -211,6 +211,78 @@ func (_c *ReleaseService_Delete_Call) RunAndReturn(run func(ctx context.Context, return _c } +// FindOrCreate provides a mock function for the type ReleaseService +func (_mock *ReleaseService) FindOrCreate(ctx context.Context, projectID uuid.UUID, name string) (models.Release, error) { + ret := _mock.Called(ctx, projectID, name) + + if len(ret) == 0 { + panic("no return value specified for FindOrCreate") + } + + var r0 models.Release + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, uuid.UUID, string) (models.Release, error)); ok { + return returnFunc(ctx, projectID, name) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, uuid.UUID, string) models.Release); ok { + r0 = returnFunc(ctx, projectID, name) + } else { + r0 = ret.Get(0).(models.Release) + } + if returnFunc, ok := ret.Get(1).(func(context.Context, uuid.UUID, string) error); ok { + r1 = returnFunc(ctx, projectID, name) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// ReleaseService_FindOrCreate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindOrCreate' +type ReleaseService_FindOrCreate_Call struct { + *mock.Call +} + +// FindOrCreate is a helper method to define mock.On call +// - ctx context.Context +// - projectID uuid.UUID +// - name string +func (_e *ReleaseService_Expecter) FindOrCreate(ctx interface{}, projectID interface{}, name interface{}) *ReleaseService_FindOrCreate_Call { + return &ReleaseService_FindOrCreate_Call{Call: _e.mock.On("FindOrCreate", ctx, projectID, name)} +} + +func (_c *ReleaseService_FindOrCreate_Call) Run(run func(ctx context.Context, projectID uuid.UUID, name string)) *ReleaseService_FindOrCreate_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 uuid.UUID + if args[1] != nil { + arg1 = args[1].(uuid.UUID) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + run( + arg0, + arg1, + arg2, + ) + }) + return _c +} + +func (_c *ReleaseService_FindOrCreate_Call) Return(release models.Release, err error) *ReleaseService_FindOrCreate_Call { + _c.Call.Return(release, err) + return _c +} + +func (_c *ReleaseService_FindOrCreate_Call) RunAndReturn(run func(ctx context.Context, projectID uuid.UUID, name string) (models.Release, error)) *ReleaseService_FindOrCreate_Call { + _c.Call.Return(run) + return _c +} + // ListByProject provides a mock function for the type ReleaseService func (_mock *ReleaseService) ListByProject(ctx context.Context, projectID uuid.UUID) ([]models.Release, error) { ret := _mock.Called(ctx, projectID) diff --git a/services/asset_service.go b/services/asset_service.go index 5a815ba56..efdeb263c 100644 --- a/services/asset_service.go +++ b/services/asset_service.go @@ -46,7 +46,7 @@ func NewAssetService(assetRepository shared.AssetRepository, dependencyVulnRepos } } -func (s *assetService) FindOrCreateAsset(ctx context.Context, rbac shared.AccessControl, providerID string, orgID uuid.UUID, projectID uuid.UUID, name string, externalEntityID string, currentUser string) (*models.Asset, error) { +func (s *assetService) FindOrCreateAsset(ctx context.Context, rbac shared.AccessControl, providerID string, orgID uuid.UUID, projectID uuid.UUID, name string, externalEntityID string, currentUser string, description string) (*models.Asset, error) { asset := &models.Asset{ Name: name, @@ -54,6 +54,7 @@ func (s *assetService) FindOrCreateAsset(ctx context.Context, rbac shared.Access ProjectID: projectID, ExternalEntityID: &externalEntityID, ExternalEntityProviderID: &providerID, + Description: description, } newAssets, _, err := s.assetRepository.UpsertSplit(ctx, nil, providerID, []*models.Asset{asset}) diff --git a/services/project_service.go b/services/project_service.go index f51a72c91..fa2f4a268 100644 --- a/services/project_service.go +++ b/services/project_service.go @@ -37,7 +37,7 @@ func (s *projectService) ReadBySlug(ctx shared.Context, organizationID uuid.UUID return project, nil } -func (s *projectService) FindOrCreateProject(ctx shared.Context, providerID string, orgID uuid.UUID, name string, externalEntityID string, parentID uuid.UUID) (*models.Project, error) { +func (s *projectService) FindOrCreateProject(ctx shared.Context, providerID string, orgID uuid.UUID, name string, externalEntityID string, parentID uuid.UUID, description string) (*models.Project, error) { project := &models.Project{ Name: name, @@ -47,6 +47,7 @@ func (s *projectService) FindOrCreateProject(ctx shared.Context, providerID stri Type: models.ProjectTypeDynamic, ExternalEntityID: &externalEntityID, ExternalEntityProviderID: &providerID, + Description: description, } newProjects, _, err := s.projectRepository.UpsertSplit(ctx.Request().Context(), nil, providerID, []*models.Project{project}) if err != nil { diff --git a/shared/common_interfaces.go b/shared/common_interfaces.go index 47cb163a8..b9d8b52dd 100644 --- a/shared/common_interfaces.go +++ b/shared/common_interfaces.go @@ -94,6 +94,8 @@ type ProjectRepository interface { Activate(ctx context.Context, tx DB, projectID uuid.UUID) error RecursivelyGetChildProjects(ctx context.Context, tx DB, projectID uuid.UUID) ([]models.Project, error) GetDirectChildProjects(ctx context.Context, tx DB, projectID uuid.UUID) ([]models.Project, error) + GetDirectChildProjectsWithProviderID(ctx context.Context, tx DB, parentID uuid.UUID, providerID string) ([]models.Project, error) + GetChildProjectsForParents(ctx context.Context, tx DB, parentIDs []uuid.UUID, providerID string) ([]models.Project, error) GetByOrgID(ctx context.Context, tx DB, organizationID uuid.UUID) ([]models.Project, error) GetProjectByAssetID(ctx context.Context, tx DB, assetID uuid.UUID) (models.Project, error) GetByProjectIDs(ctx context.Context, tx DB, projectIDs []uuid.UUID) ([]models.Project, error) @@ -107,7 +109,8 @@ type ProjectRepository interface { ListSubProjectsAndAssets(ctx context.Context, tx DB, allowedAssetIDs []string, allowedProjectIDs []uuid.UUID, parentID *uuid.UUID, orgID uuid.UUID, pageInfo PageInfo, search string, filter []FilterQuery, sort []SortQuery) (Paged[dtos.ProjectAssetDTO], error) SearchProjectsWithSubProjectsAndAssetsPaged(ctx context.Context, tx DB, allowedAssetIDs []string, allowedProjectIDs []string, parentID *uuid.UUID, orgID uuid.UUID, pageInfo PageInfo, search string, filter []FilterQuery, sort []SortQuery) (Paged[dtos.ProjectDTO], error) All(ctx context.Context, tx DB) ([]models.Project, error) - CleanupDynamicProject(ctx context.Context, tx DB, organizationID uuid.UUID, parentProjectID uuid.UUID, providerID string, projectExternalEntityID string, assetExternalEntityID string, assetVersionName string) error + GetDirectChildProjectsWithProviderIDAndExternalEntityID(ctx context.Context, tx DB, parentID uuid.UUID, providerID string, externalEntityID string) (models.Project, error) + CleanupDynamicProject(ctx context.Context, tx DB, organizationID uuid.UUID, parentProjectID uuid.UUID, providerID string, projectExternalEntityID string, assetExternalEntityID string, assetVersionName string, artifactName string) error } type Verifier interface { @@ -148,6 +151,8 @@ type AssetRepository interface { utils.Repository[uuid.UUID, models.Asset, DB] GetAllowedAssetsByProjectID(ctx context.Context, tx DB, allowedAssetIDs []string, projectID uuid.UUID) ([]models.Asset, error) GetByProjectID(ctx context.Context, tx DB, projectID uuid.UUID) ([]models.Asset, error) + GetByProjectIDs(ctx context.Context, tx DB, projectIDs []uuid.UUID) ([]models.Asset, error) + GetByProjectIDsWithProviderID(ctx context.Context, tx DB, projectIDs []uuid.UUID, providerID string) ([]models.Asset, error) GetByOrgID(ctx context.Context, tx DB, organizationID uuid.UUID) ([]models.Asset, error) FindByName(ctx context.Context, tx DB, name string) (models.Asset, error) FindAssetByExternalProviderID(ctx context.Context, tx DB, externalEntityProviderID string, externalEntityID string) (*models.Asset, error) @@ -177,6 +182,8 @@ type ArtifactRepository interface { GetAllArtifactAffectedByDependencyVuln(ctx context.Context, tx DB, vulnID uuid.UUID) ([]models.Artifact, error) GetByAssetVersions(ctx context.Context, tx DB, assetID uuid.UUID, assetVersionNames []string) ([]models.Artifact, error) CleanupOrphanedRecords(ctx context.Context) error + GetByAssetID(ctx context.Context, tx DB, assetID uuid.UUID) ([]models.Artifact, error) + GetByAssetIDs(ctx context.Context, tx DB, assetIDs []uuid.UUID) ([]models.Artifact, error) } type ReleaseRepository interface { @@ -380,7 +387,7 @@ type ProjectService interface { CreateProject(ctx Context, project *models.Project) error BootstrapProject(ctx context.Context, rbac AccessControl, project *models.Project) error SearchProjectsWithSubProjectsAndAssetsPaged(c Context) (Paged[dtos.ProjectDTO], error) - FindOrCreateProject(ctx Context, providerID string, orgID uuid.UUID, name string, externalEntityID string, parentID uuid.UUID) (*models.Project, error) + FindOrCreateProject(ctx Context, providerID string, orgID uuid.UUID, name string, externalEntityID string, parentID uuid.UUID, description string) (*models.Project, error) } type InTotoVerifierService interface { @@ -395,7 +402,7 @@ type AssetService interface { GetCVSSBadgeSVG(ctx context.Context, latest *models.ArtifactRiskHistory) string CreateAsset(ctx context.Context, rbac AccessControl, currentUserID string, asset models.Asset) (*models.Asset, error) BootstrapAsset(ctx context.Context, rbac AccessControl, asset *models.Asset) error - FindOrCreateAsset(ctx context.Context, rbac AccessControl, providerID string, orgID uuid.UUID, projectID uuid.UUID, name string, externalEntityID string, currentUser string) (*models.Asset, error) + FindOrCreateAsset(ctx context.Context, rbac AccessControl, providerID string, orgID uuid.UUID, projectID uuid.UUID, name string, externalEntityID string, currentUser string, description string) (*models.Asset, error) } type ArtifactService interface { GetArtifactsByAssetIDAndAssetVersionName(ctx context.Context, tx DB, assetID uuid.UUID, assetVersionName string) ([]models.Artifact, error) @@ -435,6 +442,7 @@ type AssetVersionRepository interface { Delete(ctx context.Context, tx DB, assetVersion *models.AssetVersion) error Save(ctx context.Context, tx DB, assetVersion *models.AssetVersion) error GetAssetVersionsByAssetID(ctx context.Context, tx DB, assetID uuid.UUID) ([]models.AssetVersion, error) + GetAssetVersionsByAssetIDs(ctx context.Context, tx DB, assetIDs []uuid.UUID) ([]models.AssetVersion, error) GetAssetVersionsByAssetIDWithArtifacts(ctx context.Context, tx DB, assetID uuid.UUID) ([]models.AssetVersion, error) GetDefaultAssetVersionsByProjectID(ctx context.Context, tx DB, projectID uuid.UUID) ([]models.AssetVersion, error) GetDefaultAssetVersionsByProjectIDs(ctx context.Context, tx DB, projectIDs []uuid.UUID) ([]models.AssetVersion, error) From 6393e0df50a66002a5ddfbf8c0096be720854c59 Mon Sep 17 00:00:00 2001 From: rafi Date: Fri, 19 Jun 2026 10:42:25 +0200 Subject: [PATCH 10/10] fix lint Signed-off-by: rafi --- database/repositories/project_repository.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/database/repositories/project_repository.go b/database/repositories/project_repository.go index 1c3999917..fd22b8d43 100644 --- a/database/repositories/project_repository.go +++ b/database/repositories/project_repository.go @@ -644,9 +644,8 @@ func (g *projectRepository) Upsert(ctx context.Context, tx *gorm.DB, t *[]*model } func (g *projectRepository) CleanupDynamicProject(ctx context.Context, tx *gorm.DB, organizationID uuid.UUID, parentProjectID uuid.UUID, providerID string, projectExternalEntityID string, assetExternalEntityID string, assetVersionName string, artifactName string) error { - var query string - query = ` + query := ` WITH target AS ( SELECT