From 479c9b9fe77a00d993037eb49c1fc0b2f1cce332 Mon Sep 17 00:00:00 2001 From: Arvind Choudhary Date: Tue, 24 Sep 2024 12:47:53 +0000 Subject: [PATCH] feat: [AH-307]: GC interface Integration, event framework; schema changes; UI changes (#2688) * fix * Merge branch 'main' into AH-307-plus-url-support-2_no_rbac * resolve PR comments * resolve PR comments * resolve PR comments * feat: [AH-346]: new api changes for version list and digest list (#2726) * feat: [AH-346]: new api changes for version list and digest list * resolve pr comments * resolve pr comments * feat: [AH-346]: new api yaml integration (#2716) * feat: [AH-346]: new api yaml integration * Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/gitness into AH-307-plus-url-support-2_no_rbac * fix wire check * fix lint issues * fix: [AH-357]: migrations * changes for global artifact listing (#2708) * changes for global artifact listing * Merge branch 'main' into AH-307-plus-url-support-2_no_rbac * merged main * Merge branch 'main' into AH-307-plus-url-support-2_no_rbac * [AH-307]: Updated lint * fix comment * add new method to spacestore * feat: [AH-307]: fix after rebase with main * [AH-307]: Removing comments * [AH-307]: linting fixes * feat: [AH-286]: define proto, interface and no-op reporter implementation to publish artifact events (#2657) * feat: [AH-286]: publish artifact event - no row found is not error * feat: [AH-286]: publish artifact event - no row found is not error * feat: [AH-286]: publish artifact event - lint errors, move publishing event outside DB transaction * feat: [AH-286]: publish artifact event - review comments * feat: [AH-286]: publish artifact event - address review comments * feat: [AH-286]: publish artifact event - keep payload generic * feat: [AH-286]: publish artifact event - as sqlite locks DB, perform db operation outside goroutine publishing of events * feat: [AH-286]: publish artifact event - make publishing event async * feat: [AH-286]: publish artifact event - use api types * feat: [AH-286]: Publish event for SSCA to trigger scans - no need to export spacePathStore * feat: [AH-286]: Publish event for SSCA to trigger scans - send spacePath instead of parentID * feat: [AH-286]: Publish event for SSCA to trigger scans - rename scanner as generic reporter * feat: [AH-286]: Publish event for SSCA to trigger scans - rename scanner as generic reporter * feat: [AH-286]: publish artifact event - reuse redis.Send() * feat: [AH-286]: Publish event for SSCA to trigger scans - review comments * feat: [AH-286]: Publish event for SSCA to trigger scans - remove unused interface * feat: [AH-286]: Publish event for SSCA to trigger scans - update msg format * feat: [AH-286]: define proto, interface and no-op reporter implementation to publish artifact events * feat: [AH-286]: Publish event for SSCA to trigger scans - extract acctID/orgID/projectID from spacepathStore * feat: [AH-286]: publish artifact event - remove protobuf reference, fix lint errors * feat: [AH-286]: publish artifact event - fix msg format * feat: [AH-286]: define proto, interface and no-op reporter implementation to publish artifact events * feat: [AH-286]: define proto, interface and no-op reporter implementation to publish artifact events * feat: [AH-321]: make repo form disabled for rbac (#2687) * feat: [AH-321]: make repo form disabled for rbac * fix wire-gen * GC refactoring * feat: [AH-340]: update UI as per the product feedbacks (#2685) * feat: [AH-340]: update UI as per the product feedbacks * feat: [AH-44]: add module data while redirecting to pipeline execution page * feat: [AH-44]: add build pipeline details in overview cards * feat: [AH-44]: update view for prod and non prod tag * feat: [AH-44]: rearrange filters on artifact list apge * feat: [AH-10]: add schema for overview cards, update artifact list, add ai search input, update api for registry artifact list and update mapping for deployments table * feat: [AH-307]: add secretSpacePath in upstream password field while sending to BE (#2631) * feat: [AH-307]: add secretSpacePath in upstream password field while sending to BE * feat: [AH-299]: support new changes for artifact list page (#2630) * feat: update har service api version * feat: [AH-30]: integrate API schema for deployments list content * feat: [AH-300]: update tag colors for prod and non prod tags * feat: [AH-300]: Add Deployments table in artiface version details page * feat: [AH-299]: support new changes for artifact list page * feat: [AH-299]: support new changes for artifact list page * feat: [AH-321]: support artifact registry rbac permission on UI (#2671) * feat: [AH-321]: support artifact registry rbac permission on UI * enable rbac (#2664) * fix scope * enable rbac * feat: [AH-307]: hide code tab from version details page for both docker and helm * feat: [AH-240]: add custom handling for enterprise auth type field * Merge branch 'AH-307-plus-url-support-2_no_rbac' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/gitness into AH-307-plus-url-support-2_no_rbac * feat: [AH-307]: send space_ref in query param while creating registries * lowercase rootRef * [AH-307]: updated route * [AH-307]: Added logs * [AH-307]: Added logs * feat: [AH-317]: add space_ref query param * local * Merge commit * Merge commit * Merge commit * Added comments * Revert changes * Merge commit * Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/gitness into AH-307-plus-url-support-2 * Merge branch 'AH-306d' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/gitness into AH-307-plus-url-support-2 * fix space path handling * Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/gitness into AH-307-plus-url-support-2 * Updated URLs to support slashes with + separator * fix: [AH-306c]: fix anonymous flow * fix: [AH-306c]: fix anonymous flow * feat: [AH-307]: plus url support on UI (cherry picked from commit 3fb6add3ce03498b6668b5f8f6d547e1acedaec4) * [AH-307]: Added examples (cherry picked from commit e83e41303da536f421be333be04aed09fbf75f5f) * [AH-307]: Added Regex request rewrite support (cherry picked from commit ed7b155256bdcd1134bc228b5705556a1233add6) * fix: [AH-306c]: fix anonymous flow --- .local.env | 2 +- app/store/cache/path.go | 2 +- app/store/database.go | 141 ++++--- app/store/database/space.go | 72 +++- cmd/gitness/wire.go | 2 + cmd/gitness/wire_gen.go | 7 +- .../controller/metadata/artifact_mapper.go | 80 +++- registry/app/api/controller/metadata/base.go | 61 ++-- .../api/controller/metadata/get_artifacts.go | 59 ++- .../metadata/get_artifacts_labels.go | 18 +- .../metadata/get_artifacts_versions.go | 19 +- .../api/controller/metadata/get_registries.go | 19 +- .../metadata/get_registry_artifacts.go | 102 ++++++ .../controller/metadata/update_artifact.go | 18 +- registry/app/api/controller/metadata/utils.go | 2 +- registry/app/api/handler/oci/get_token.go | 13 +- registry/app/api/openapi/api.yaml | 122 ++++++- .../contracts/artifact/services.gen.go | 345 ++++++++++++++---- .../openapi/contracts/artifact/types.gen.go | 79 +++- registry/app/api/wire.go | 8 +- registry/app/driver/filesystem/driver.go | 3 +- registry/app/event/reporter.go | 66 ++++ registry/app/pkg/docker/app.go | 3 +- registry/app/pkg/docker/helpers.go | 2 +- registry/app/pkg/docker/manifest_service.go | 213 +++++++---- registry/app/pkg/docker/wire.go | 17 +- registry/app/store/database.go | 9 +- registry/app/store/database/tag.go | 165 +++++---- registry/gc/garbagecollector.go | 2 +- registry/gc/interface.go | 2 +- registry/types/gc.go | 18 +- registry/types/tag.go | 1 + web/package.json | 2 +- web/src/ar/MFEAppTypes.ts | 16 +- .../ar/__mocks__/components/RbacButton.tsx | 29 ++ web/src/ar/app/GitnessApp.tsx | 5 +- web/src/ar/common/constants.ts | 19 +- web/src/ar/common/permissionTypes.ts | 10 +- web/src/ar/common/types.ts | 4 +- .../AISearchInput/AISearchInput.module.scss | 55 +++ .../AISearchInput.module.scss.d.ts | 24 ++ .../AISearchInput/AISearchInput.tsx | 103 ++++++ web/src/ar/components/AISearchInput/types.ts | 21 ++ .../ar/components/ButtonTabs/ButtonTabs.tsx | 5 +- .../EnvironmentTypeSelector.tsx | 45 +++ .../Form/PatternInput/PatternInput.tsx | 3 +- .../IncludeExcludePatterns.tsx | 2 + .../components/NameDescriptionTags/index.tsx | 14 +- .../ar/components/PageTitle/ArtifactTags.tsx | 12 +- .../TableCells/TableCells.module.scss | 29 +- .../TableCells/TableCells.module.scss.d.ts | 4 + .../ar/components/TableCells/TableCells.tsx | 99 ++++- web/src/ar/components/Tag/Tag.module.scss | 16 + .../ar/components/Tag/Tag.module.scss.d.ts | 3 + web/src/ar/components/Tag/Tags.tsx | 41 +++ .../ArtifactActions/DeleteRepository.tsx | 4 +- .../ArtifactActions/EditRepository.tsx | 4 +- .../ArtifactActions/SetupClient.tsx | 4 +- .../ArtifactDetailsHeaderContent.tsx | 8 + .../ArtifactListPage.module.scss | 9 + .../ArtifactListPage.module.scss.d.ts | 1 + .../pages/artifact-list/ArtifactListPage.tsx | 174 +++++---- .../RegistryArtifactListPage.tsx | 161 ++++++++ .../ArtifactListTable.module.scss | 10 +- .../ArtifactListTable.module.scss.d.ts | 2 + .../ArtifactListTable/ArtifactListTable.tsx | 42 ++- .../ArtifactListTableCell.tsx | 216 +++++++---- .../ArtifactSearchInput.tsx | 29 ++ .../ArtifactSearchInput/constants.ts | 37 ++ .../RegistryArtifactListTable.module.scss | 47 +++ ...RegistryArtifactListTable.module.scss.d.ts | 21 ++ .../RegistryArtifactListTable.tsx | 112 ++++++ .../RegistryArtifactListTableCell.tsx | 136 +++++++ .../RegistryArtifactListTable/utils.ts | 52 +++ web/src/ar/pages/artifact-list/constants.ts | 20 + .../artifact-list/strings/strings.en.yaml | 17 +- web/src/ar/pages/artifact-list/utils.ts | 22 +- .../DigestListTable.module.scss | 2 +- .../DigestListTable/DigestListTable.tsx | 7 + .../DigestListTable/DigestTableCells.tsx | 45 ++- .../pages/digest-list/strings/strings.en.yaml | 3 +- .../repository-details/RepositoryDetails.tsx | 37 +- .../components/Actions/DeleteRepository.tsx | 4 +- .../Actions/QuarantineRepository.tsx | 4 +- .../components/Actions/RestoreRepository.tsx | 4 +- .../components/Actions/ScanRepository.tsx | 4 +- .../components/Actions/SetupClient.tsx | 4 +- .../Forms/RepositoryConfigurationForm.tsx | 5 +- .../components/Forms/RepositoryCreateForm.tsx | 3 + .../context/RepositoryProvider.tsx | 11 +- .../useCreateRepositoryModal.tsx | 78 ++-- .../repository-list/RepositoryListPage.tsx | 4 +- .../CreateRepositoryButton.tsx | 4 +- web/src/ar/pages/repository-list/utils.ts | 4 +- .../DockerRepositoryUrlInput.tsx | 1 + ...UpstreamProxyAuthenticationFormContent.tsx | 1 + .../Forms/UpstreamProxyConfigurationForm.tsx | 34 +- .../Forms/UpstreamProxyCreateForm.tsx | 28 +- .../components/Forms/utils.ts | 34 +- .../DeleteUpstreamProxy.tsx | 4 +- .../RestoreUpstreamProxy.tsx | 4 +- .../ScanUpstreamProxy.tsx | 4 +- .../useCreateUpstreamProxyModal.tsx | 76 ++-- .../DockerArtifactSSCAContent.tsx | 18 +- .../DockerArtifactSecurityTestsContent.tsx | 18 +- .../DockerDeploymentsContent.tsx | 160 ++++++++ .../DockerVersion/DockerVersion.module.scss | 14 +- .../DockerVersion.module.scss.d.ts | 2 + .../DockerVersion/DockerVersionType.tsx | 3 + .../DeploymentOverviewCards.module.scss | 19 + .../DeploymentOverviewCards.module.scss.d.ts | 19 + .../DeploymentOverviewCards.tsx | 69 ++++ .../DeploymentsTable.module.scss | 33 ++ .../DeploymentsTable.module.scss.d.ts | 19 + .../DeploymentsTable/DeploymentsTable.tsx | 117 ++++++ .../DeploymentsTableCells.tsx | 131 +++++++ .../components/DeploymentsTable/types.ts | 19 + .../components/DeploymentsTable/utils.ts | 46 +++ .../OverviewCards/OverviewCards.module.scss | 6 + .../OverviewCards.module.scss.d.ts | 1 + .../OverviewCards/OverviewCards.tsx | 145 +++++--- .../DeploymentsCard/DeploymentsCard.tsx | 25 +- .../SecurityTestsCard/SecurityItem.tsx | 2 +- .../SupplyChainCard/SupplyChainCard.tsx | 2 +- .../VersionDetailsTab.module.scss | 43 +-- .../VersionDetailsTab.module.scss.d.ts | 4 - .../VersionDetailsTabs/VersionDetailsTabs.tsx | 20 +- .../VersionDetailsTabs/constants.tsx | 39 +- .../context/VersionProvider.tsx | 12 +- .../version-details/strings/strings.en.yaml | 21 +- .../DockerVersionListTable.module.scss | 2 +- .../VersionListTable/VersionListCell.tsx | 13 +- .../VersionListTable.module.scss | 2 +- web/src/ar/strings/strings.en.yaml | 5 + web/src/ar/strings/types.ts | 26 ++ web/yarn.lock | 8 +- 136 files changed, 3853 insertions(+), 980 deletions(-) create mode 100644 registry/app/api/controller/metadata/get_registry_artifacts.go create mode 100644 registry/app/event/reporter.go create mode 100644 web/src/ar/__mocks__/components/RbacButton.tsx create mode 100644 web/src/ar/components/AISearchInput/AISearchInput.module.scss create mode 100644 web/src/ar/components/AISearchInput/AISearchInput.module.scss.d.ts create mode 100644 web/src/ar/components/AISearchInput/AISearchInput.tsx create mode 100644 web/src/ar/components/AISearchInput/types.ts create mode 100644 web/src/ar/components/EnvironmentTypeSelector/EnvironmentTypeSelector.tsx create mode 100644 web/src/ar/components/Tag/Tags.tsx create mode 100644 web/src/ar/pages/artifact-list/RegistryArtifactListPage.tsx create mode 100644 web/src/ar/pages/artifact-list/components/ArtifactSearchInput/ArtifactSearchInput.tsx create mode 100644 web/src/ar/pages/artifact-list/components/ArtifactSearchInput/constants.ts create mode 100644 web/src/ar/pages/artifact-list/components/RegistryArtifactListTable/RegistryArtifactListTable.module.scss create mode 100644 web/src/ar/pages/artifact-list/components/RegistryArtifactListTable/RegistryArtifactListTable.module.scss.d.ts create mode 100644 web/src/ar/pages/artifact-list/components/RegistryArtifactListTable/RegistryArtifactListTable.tsx create mode 100644 web/src/ar/pages/artifact-list/components/RegistryArtifactListTable/RegistryArtifactListTableCell.tsx create mode 100644 web/src/ar/pages/artifact-list/components/RegistryArtifactListTable/utils.ts create mode 100644 web/src/ar/pages/artifact-list/constants.ts create mode 100644 web/src/ar/pages/version-details/DockerVersion/DockerDeploymentsContent.tsx create mode 100644 web/src/ar/pages/version-details/DockerVersion/components/DeploymentOverviewCards/DeploymentOverviewCards.module.scss create mode 100644 web/src/ar/pages/version-details/DockerVersion/components/DeploymentOverviewCards/DeploymentOverviewCards.module.scss.d.ts create mode 100644 web/src/ar/pages/version-details/DockerVersion/components/DeploymentOverviewCards/DeploymentOverviewCards.tsx create mode 100644 web/src/ar/pages/version-details/DockerVersion/components/DeploymentsTable/DeploymentsTable.module.scss create mode 100644 web/src/ar/pages/version-details/DockerVersion/components/DeploymentsTable/DeploymentsTable.module.scss.d.ts create mode 100644 web/src/ar/pages/version-details/DockerVersion/components/DeploymentsTable/DeploymentsTable.tsx create mode 100644 web/src/ar/pages/version-details/DockerVersion/components/DeploymentsTable/DeploymentsTableCells.tsx create mode 100644 web/src/ar/pages/version-details/DockerVersion/components/DeploymentsTable/types.ts create mode 100644 web/src/ar/pages/version-details/DockerVersion/components/DeploymentsTable/utils.ts diff --git a/.local.env b/.local.env index da7b6efa1..88b04fa54 100644 --- a/.local.env +++ b/.local.env @@ -13,4 +13,4 @@ GITNESS_SSH_HOST=localhost GITNESS_SSH_PORT=2222 GITNESS_REGISTRY_STORAGE_TYPE=filesystem -GITNESS_REGISTRY_FILESYSTEM_ROOT_DIRECTORY=/tmp +GITNESS_REGISTRY_FILESYSTEM_ROOT_DIRECTORY=/tmp \ No newline at end of file diff --git a/app/store/cache/path.go b/app/store/cache/path.go index cdff0fd2b..7917a2aa9 100644 --- a/app/store/cache/path.go +++ b/app/store/cache/path.go @@ -24,7 +24,7 @@ import ( "github.com/harness/gitness/types" ) -// pathCacheGetter is used to hook a SpacePathStore as source of a PathCache. +// pathCacheGetter is used to hook a spacePathStore as source of a PathCache. // IMPORTANT: It assumes that the pathCache already transformed the key. type pathCacheGetter struct { spacePathStore store.SpacePathStore diff --git a/app/store/database.go b/app/store/database.go index 88787312d..fdfc2f1ec 100644 --- a/app/store/database.go +++ b/app/store/database.go @@ -94,12 +94,16 @@ type ( DeleteServiceAccount(ctx context.Context, id int64) error // ListServiceAccounts returns a list of service accounts for a specific parent. - ListServiceAccounts(ctx context.Context, - parentType enum.ParentResourceType, parentID int64) ([]*types.ServiceAccount, error) + ListServiceAccounts( + ctx context.Context, + parentType enum.ParentResourceType, parentID int64, + ) ([]*types.ServiceAccount, error) // CountServiceAccounts returns a count of service accounts for a specific parent. - CountServiceAccounts(ctx context.Context, - parentType enum.ParentResourceType, parentID int64) (int64, error) + CountServiceAccounts( + ctx context.Context, + parentType enum.ParentResourceType, parentID int64, + ) (int64, error) /* * SERVICE RELATED OPERATIONS. @@ -167,6 +171,9 @@ type ( // FindByRef finds the space using the spaceRef as either the id or the space path. FindByRef(ctx context.Context, spaceRef string) (*types.Space, error) + // FindByRefCaseInsensitive finds the space using the spaceRef. + FindByRefCaseInsensitive(ctx context.Context, spaceRef string) (*types.Space, error) + // FindByRefAndDeletedAt finds the space using the spaceRef and deleted timestamp. FindByRefAndDeletedAt(ctx context.Context, spaceRef string, deletedAt int64) (*types.Space, error) @@ -192,8 +199,10 @@ type ( Update(ctx context.Context, space *types.Space) error // UpdateOptLock updates the space using the optimistic locking mechanism. - UpdateOptLock(ctx context.Context, space *types.Space, - mutateFn func(space *types.Space) error) (*types.Space, error) + UpdateOptLock( + ctx context.Context, space *types.Space, + mutateFn func(space *types.Space) error, + ) (*types.Space, error) // FindForUpdate finds the space and locks it for an update. FindForUpdate(ctx context.Context, id int64) (*types.Space, error) @@ -205,8 +214,10 @@ type ( Purge(ctx context.Context, id int64, deletedAt *int64) error // Restore restores a soft deleted space. - Restore(ctx context.Context, space *types.Space, - newIdentifier *string, newParentID *int64) (*types.Space, error) + Restore( + ctx context.Context, space *types.Space, + newIdentifier *string, newParentID *int64, + ) (*types.Space, error) // Count the child spaces of a space. Count(ctx context.Context, id int64, opts *types.SpaceFilter) (int64, error) @@ -239,8 +250,10 @@ type ( GetSize(ctx context.Context, id int64) (int64, error) // UpdateOptLock the repo details using the optimistic locking mechanism. - UpdateOptLock(ctx context.Context, repo *types.Repository, - mutateFn func(repository *types.Repository) error) (*types.Repository, error) + UpdateOptLock( + ctx context.Context, repo *types.Repository, + mutateFn func(repository *types.Repository) error, + ) (*types.Repository, error) // SoftDelete a repo. SoftDelete(ctx context.Context, repo *types.Repository, deletedAt int64) error @@ -249,8 +262,10 @@ type ( Purge(ctx context.Context, id int64, deletedAt *int64) error // Restore a deleted repo using the optimistic locking mechanism. - Restore(ctx context.Context, repo *types.Repository, - newIdentifier *string, newParentID *int64) (*types.Repository, error) + Restore( + ctx context.Context, repo *types.Repository, + newIdentifier *string, newParentID *int64, + ) (*types.Repository, error) // Count of active repos in a space. With "DeletedBeforeOrAt" filter, counts deleted repos. Count(ctx context.Context, parentID int64, opts *types.RepoFilter) (int64, error) @@ -308,7 +323,11 @@ type ( CountUsers(ctx context.Context, spaceID int64, filter types.MembershipUserFilter) (int64, error) ListUsers(ctx context.Context, spaceID int64, filter types.MembershipUserFilter) ([]types.MembershipUser, error) CountSpaces(ctx context.Context, userID int64, filter types.MembershipSpaceFilter) (int64, error) - ListSpaces(ctx context.Context, userID int64, filter types.MembershipSpaceFilter) ([]types.MembershipSpace, error) + ListSpaces( + ctx context.Context, + userID int64, + filter types.MembershipSpaceFilter, + ) ([]types.MembershipSpace, error) } // PublicAccessStore defines the publicly accessible resources data storage. @@ -362,8 +381,10 @@ type ( Update(ctx context.Context, pr *types.PullReq) error // UpdateOptLock the pull request details using the optimistic locking mechanism. - UpdateOptLock(ctx context.Context, pr *types.PullReq, - mutateFn func(pr *types.PullReq) error) (*types.PullReq, error) + UpdateOptLock( + ctx context.Context, pr *types.PullReq, + mutateFn func(pr *types.PullReq) error, + ) (*types.PullReq, error) // UpdateActivitySeq the pull request's activity sequence number. // It will set new values to the ActivitySeq, Version and Updated fields. @@ -395,7 +416,8 @@ type ( Create(ctx context.Context, act *types.PullReqActivity) error // CreateWithPayload create a new system activity from the provided payload. - CreateWithPayload(ctx context.Context, + CreateWithPayload( + ctx context.Context, pr *types.PullReq, principalID int64, payload types.PullReqActivityPayload, @@ -406,7 +428,8 @@ type ( Update(ctx context.Context, act *types.PullReqActivity) error // UpdateOptLock updates the pull request activity using the optimistic locking mechanism. - UpdateOptLock(ctx context.Context, + UpdateOptLock( + ctx context.Context, act *types.PullReqActivity, mutateFn func(act *types.PullReqActivity) error, ) (*types.PullReqActivity, error) @@ -543,8 +566,10 @@ type ( Update(ctx context.Context, hook *types.Webhook) error // UpdateOptLock updates the webhook using the optimistic locking mechanism. - UpdateOptLock(ctx context.Context, hook *types.Webhook, - mutateFn func(hook *types.Webhook) error) (*types.Webhook, error) + UpdateOptLock( + ctx context.Context, hook *types.Webhook, + mutateFn func(hook *types.Webhook) error, + ) (*types.Webhook, error) // Delete deletes the webhook for the given id. Delete(ctx context.Context, id int64) error @@ -553,12 +578,16 @@ type ( DeleteByIdentifier(ctx context.Context, parentType enum.WebhookParent, parentID int64, identifier string) error // Count counts the webhooks for a given parent type and id. - Count(ctx context.Context, parentType enum.WebhookParent, parentID int64, - opts *types.WebhookFilter) (int64, error) + Count( + ctx context.Context, parentType enum.WebhookParent, parentID int64, + opts *types.WebhookFilter, + ) (int64, error) // List lists the webhooks for a given parent type and id. - List(ctx context.Context, parentType enum.WebhookParent, parentID int64, - opts *types.WebhookFilter) ([]*types.Webhook, error) + List( + ctx context.Context, parentType enum.WebhookParent, parentID int64, + opts *types.WebhookFilter, + ) ([]*types.Webhook, error) } // WebhookExecutionStore defines the webhook execution data storage. @@ -573,8 +602,10 @@ type ( DeleteOld(ctx context.Context, olderThan time.Time) (int64, error) // ListForWebhook lists the webhook executions for a given webhook id. - ListForWebhook(ctx context.Context, webhookID int64, - opts *types.WebhookExecutionFilter) ([]*types.WebhookExecution, error) + ListForWebhook( + ctx context.Context, webhookID int64, + opts *types.WebhookExecutionFilter, + ) ([]*types.WebhookExecution, error) // ListForTrigger lists the webhook executions for a given trigger id. ListForTrigger(ctx context.Context, triggerID string) ([]*types.WebhookExecution, error) @@ -646,7 +677,10 @@ type ( List(ctx context.Context, filter *types.GitspaceFilter) ([]*types.GitspaceInstance, error) // List lists the latest gitspace instance present for the gitspace configs in the datastore. - FindAllLatestByGitspaceConfigID(ctx context.Context, gitspaceConfigIDs []int64) ([]*types.GitspaceInstance, error) + FindAllLatestByGitspaceConfigID( + ctx context.Context, + gitspaceConfigIDs []int64, + ) ([]*types.GitspaceInstance, error) } InfraProviderConfigStore interface { @@ -677,7 +711,8 @@ type ( Update(ctx context.Context, infraProviderResource *types.InfraProviderResource) error // List lists the infra provider resource present for the gitspace config in a parent space ID in the datastore. - List(ctx context.Context, + List( + ctx context.Context, infraProviderConfigID int64, filter types.ListQueryFilter, ) ([]*types.InfraProviderResource, error) @@ -707,8 +742,10 @@ type ( ListLatest(ctx context.Context, repoID int64, pagination types.ListQueryFilter) ([]*types.Pipeline, error) // UpdateOptLock updates the pipeline using the optimistic locking mechanism. - UpdateOptLock(ctx context.Context, pipeline *types.Pipeline, - mutateFn func(pipeline *types.Pipeline) error) (*types.Pipeline, error) + UpdateOptLock( + ctx context.Context, pipeline *types.Pipeline, + mutateFn func(pipeline *types.Pipeline) error, + ) (*types.Pipeline, error) // Delete deletes a pipeline ID from the datastore. Delete(ctx context.Context, id int64) error @@ -743,8 +780,10 @@ type ( Count(ctx context.Context, spaceID int64, pagination types.ListQueryFilter) (int64, error) // UpdateOptLock updates the secret using the optimistic locking mechanism. - UpdateOptLock(ctx context.Context, secret *types.Secret, - mutateFn func(secret *types.Secret) error) (*types.Secret, error) + UpdateOptLock( + ctx context.Context, secret *types.Secret, + mutateFn func(secret *types.Secret) error, + ) (*types.Secret, error) // Update tries to update a secret. Update(ctx context.Context, secret *types.Secret) error @@ -837,8 +876,10 @@ type ( Count(ctx context.Context, spaceID int64, pagination types.ListQueryFilter) (int64, error) // UpdateOptLock updates the connector using the optimistic locking mechanism. - UpdateOptLock(ctx context.Context, connector *types.Connector, - mutateFn func(connector *types.Connector) error) (*types.Connector, error) + UpdateOptLock( + ctx context.Context, connector *types.Connector, + mutateFn func(connector *types.Connector) error, + ) (*types.Connector, error) // Update tries to update a connector. Update(ctx context.Context, connector *types.Connector) error @@ -858,8 +899,10 @@ type ( Find(ctx context.Context, id int64) (*types.Template, error) // FindByIdentifierAndType returns a template given a space ID, identifier and a type - FindByIdentifierAndType(ctx context.Context, spaceID int64, - identifier string, resolverType enum.ResolverType) (*types.Template, error) + FindByIdentifierAndType( + ctx context.Context, spaceID int64, + identifier string, resolverType enum.ResolverType, + ) (*types.Template, error) // Create creates a new template. Create(ctx context.Context, template *types.Template) error @@ -868,8 +911,10 @@ type ( Count(ctx context.Context, spaceID int64, pagination types.ListQueryFilter) (int64, error) // UpdateOptLock updates the template using the optimistic locking mechanism. - UpdateOptLock(ctx context.Context, template *types.Template, - mutateFn func(template *types.Template) error) (*types.Template, error) + UpdateOptLock( + ctx context.Context, template *types.Template, + mutateFn func(template *types.Template) error, + ) (*types.Template, error) // Update tries to update a template. Update(ctx context.Context, template *types.Template) error @@ -878,7 +923,12 @@ type ( Delete(ctx context.Context, id int64) error // DeleteByIdentifierAndType deletes a template given a space ID, identifier and a type. - DeleteByIdentifierAndType(ctx context.Context, spaceID int64, identifier string, resolverType enum.ResolverType) error + DeleteByIdentifierAndType( + ctx context.Context, + spaceID int64, + identifier string, + resolverType enum.ResolverType, + ) error // List lists the templates in a given space. List(ctx context.Context, spaceID int64, filter types.ListQueryFilter) ([]*types.Template, error) @@ -895,8 +945,10 @@ type ( Update(ctx context.Context, trigger *types.Trigger) error // UpdateOptLock updates the trigger using the optimistic locking mechanism. - UpdateOptLock(ctx context.Context, trigger *types.Trigger, - mutateFn func(trigger *types.Trigger) error) (*types.Trigger, error) + UpdateOptLock( + ctx context.Context, trigger *types.Trigger, + mutateFn func(trigger *types.Trigger) error, + ) (*types.Trigger, error) // List lists the triggers for a given pipeline ID. List(ctx context.Context, pipelineID int64, filter types.ListQueryFilter) ([]*types.Trigger, error) @@ -941,7 +993,11 @@ type ( Map(ctx context.Context, ids []int64) (map[int64]*types.UserGroup, error) // FindManyByIdentifiersAndSpaceID returns a list of usergroups - FindManyByIdentifiersAndSpaceID(ctx context.Context, identifiers []string, spaceID int64) ([]*types.UserGroup, error) + FindManyByIdentifiersAndSpaceID( + ctx context.Context, + identifiers []string, + spaceID int64, + ) ([]*types.UserGroup, error) // FindManyByIDs returns a list of usergroups searching them via ids FindManyByIDs(ctx context.Context, ids []int64) ([]*types.UserGroup, error) @@ -1145,7 +1201,8 @@ type ( spaceID int64, gitspaceInstanceID int64, ) (*types.InfraProvisioned, error) - FindLatestByGitspaceInstanceIdentifier(ctx context.Context, + FindLatestByGitspaceInstanceIdentifier( + ctx context.Context, spaceID int64, gitspaceInstanceIdentifier string, ) (*types.InfraProvisioned, error) diff --git a/app/store/database/space.go b/app/store/database/space.go index 796ac156f..556144874 100644 --- a/app/store/database/space.go +++ b/app/store/database/space.go @@ -125,6 +125,40 @@ func (s *SpaceStore) FindByRef(ctx context.Context, spaceRef string) (*types.Spa return s.findByRef(ctx, spaceRef, nil) } +// FindByRefCaseInsensitive finds the space using the spaceRef. +func (s *SpaceStore) FindByRefCaseInsensitive(ctx context.Context, spaceRef string) (*types.Space, error) { + segments := paths.Segments(spaceRef) + if len(segments) < 1 { + return nil, fmt.Errorf("invalid space reference provided") + } + + var stmt squirrel.SelectBuilder + switch { + case len(segments) == 1: + stmt = database.Builder. + Select("space_id"). + From("spaces"). + Where("LOWER(space_uid) = LOWER(?) ", segments[0]) + + case len(segments) > 1: + stmt = buildRecursiveSelectQueryUsingCaseInsensitivePath(segments) + } + + sql, args, err := stmt.ToSql() + if err != nil { + return nil, fmt.Errorf("failed to create sql query: %w", err) + } + + db := dbtx.GetAccessor(ctx, s.db) + + var spaceID int64 + if err = db.GetContext(ctx, &spaceID, sql, args...); err != nil { + return nil, database.ProcessSQLErrorf(ctx, err, "Failed executing custom select query") + } + + return s.find(ctx, spaceID, nil) +} + // FindByRefAndDeletedAt finds the space using the spaceRef as either the id or the space path and deleted timestamp. func (s *SpaceStore) FindByRefAndDeletedAt( ctx context.Context, @@ -701,7 +735,8 @@ func (s *SpaceStore) list( return s.mapToSpaces(ctx, s.db, dst) } -func (s *SpaceStore) listAll(ctx context.Context, +func (s *SpaceStore) listAll( + ctx context.Context, id int64, opts *types.SpaceFilter, ) ([]*types.Space, error) { @@ -901,7 +936,7 @@ func mapToInternalSpace(s *types.Space) *space { // buildRecursiveSelectQueryUsingPath builds the recursive select query using path among active or soft deleted spaces. func buildRecursiveSelectQueryUsingPath(segments []string, deletedAt int64) squirrel.SelectBuilder { - leaf := "s" + fmt.Sprint(len(segments)-1) + leaf := "s" + strconv.Itoa(len(segments)-1) // add the current space (leaf) stmt := database.Builder. @@ -910,10 +945,37 @@ func buildRecursiveSelectQueryUsingPath(segments []string, deletedAt int64) squi Where(leaf+".space_uid = ? AND "+leaf+".space_deleted = ?", segments[len(segments)-1], deletedAt) for i := len(segments) - 2; i >= 0; i-- { - parentAlias := "s" + fmt.Sprint(i) - alias := "s" + fmt.Sprint(i+1) + parentAlias := "s" + strconv.Itoa(i) + alias := "s" + strconv.Itoa(i+1) - stmt = stmt.InnerJoin(fmt.Sprintf("spaces %s ON %s.space_id = %s.space_parent_id", parentAlias, parentAlias, alias)). + stmt = stmt.InnerJoin(fmt.Sprintf("spaces %s ON %s.space_id = %s.space_parent_id", parentAlias, parentAlias, + alias)). + Where(parentAlias+".space_uid = ?", segments[i]) + } + + // add parent check for root + stmt = stmt.Where("s0.space_parent_id IS NULL") + + return stmt +} + +// buildRecursiveSelectQueryUsingCaseInsensitivePath builds the recursive select query using path among active or soft +// deleted spaces. +func buildRecursiveSelectQueryUsingCaseInsensitivePath(segments []string) squirrel.SelectBuilder { + leaf := "s" + strconv.Itoa(len(segments)-1) + + // add the current space (leaf) + stmt := database.Builder. + Select(leaf+".space_id"). + From("spaces "+leaf). + Where("LOWER("+leaf+".space_uid) = LOWER(?)", segments[len(segments)-1]) + + for i := len(segments) - 2; i >= 0; i-- { + parentAlias := "s" + strconv.Itoa(i) + alias := "s" + strconv.Itoa(i+1) + + stmt = stmt.InnerJoin(fmt.Sprintf("spaces %s ON %s.space_id = %s.space_parent_id", parentAlias, parentAlias, + alias)). Where(parentAlias+".space_uid = ?", segments[i]) } diff --git a/cmd/gitness/wire.go b/cmd/gitness/wire.go index 7fadfbc5f..d6bfbf9c5 100644 --- a/cmd/gitness/wire.go +++ b/cmd/gitness/wire.go @@ -129,6 +129,7 @@ import ( "github.com/harness/gitness/livelog" "github.com/harness/gitness/lock" "github.com/harness/gitness/pubsub" + "github.com/harness/gitness/registry/app/pkg/docker" "github.com/harness/gitness/ssh" "github.com/harness/gitness/store/database/dbtx" "github.com/harness/gitness/types" @@ -270,6 +271,7 @@ func initSystem(ctx context.Context, config *types.Config) (*cliserver.System, e aiagent.WireSet, capabilities.WireSet, capabilitiesservice.WireSet, + docker.ProvideReporter, secretservice.WireSet, containerGit.WireSet, containerUser.WireSet, diff --git a/cmd/gitness/wire_gen.go b/cmd/gitness/wire_gen.go index 39aad4827..10fcbb7d0 100644 --- a/cmd/gitness/wire_gen.go +++ b/cmd/gitness/wire_gen.go @@ -432,16 +432,17 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro mediaTypesRepository := database2.ProvideMediaTypeDao(db) blobRepository := database2.ProvideBlobDao(db, mediaTypesRepository) storageService := docker.StorageServiceProvider(config, storageDriver) - manifestRepository := database2.ProvideManifestDao(db, mediaTypesRepository) gcService := gc.ServiceProvider() - app := docker.NewApp(ctx, db, storageDeleter, blobRepository, spaceStore, config, storageService, mediaTypesRepository, manifestRepository, gcService) + app := docker.NewApp(ctx, db, storageDeleter, blobRepository, spaceStore, config, storageService, gcService) registryRepository := database2.ProvideRepoDao(db, mediaTypesRepository) + manifestRepository := database2.ProvideManifestDao(db, mediaTypesRepository) manifestReferenceRepository := database2.ProvideManifestRefDao(db) tagRepository := database2.ProvideTagDao(db) imageRepository := database2.ProvideImageDao(db) artifactRepository := database2.ProvideArtifactDao(db) layerRepository := database2.ProvideLayerDao(db, mediaTypesRepository) - manifestService := docker.ManifestServiceProvider(registryRepository, manifestRepository, blobRepository, mediaTypesRepository, manifestReferenceRepository, tagRepository, imageRepository, artifactRepository, layerRepository, gcService, transactor) + eventReporter := docker.ProvideReporter() + manifestService := docker.ManifestServiceProvider(registryRepository, manifestRepository, blobRepository, mediaTypesRepository, manifestReferenceRepository, tagRepository, imageRepository, artifactRepository, layerRepository, gcService, transactor, eventReporter, spacePathStore) registryBlobRepository := database2.ProvideRegistryBlobDao(db) bandwidthStatRepository := database2.ProvideBandwidthStatDao(db) downloadStatRepository := database2.ProvideDownloadStatDao(db) diff --git a/registry/app/api/controller/metadata/artifact_mapper.go b/registry/app/api/controller/metadata/artifact_mapper.go index cf788b3c9..738fe89f5 100644 --- a/registry/app/api/controller/metadata/artifact_mapper.go +++ b/registry/app/api/controller/metadata/artifact_mapper.go @@ -25,19 +25,53 @@ import ( "github.com/rs/zerolog/log" ) -func GetArtifactMetadata(artifacts *[]types.ArtifactMetadata) []artifactapi.ArtifactMetadata { - artifactMetadataList := make([]artifactapi.ArtifactMetadata, 0, len(*artifacts)) - for _, artifact := range *artifacts { - artifactMetadata := mapToArtifactMetadata(artifact) +func GetArtifactMetadata( + artifacts []types.ArtifactMetadata, + rootIdentifier string, + registryURL string, +) []artifactapi.ArtifactMetadata { + artifactMetadataList := make([]artifactapi.ArtifactMetadata, 0, len(artifacts)) + for _, artifact := range artifacts { + artifactMetadata := mapToArtifactMetadata(artifact, rootIdentifier, registryURL) artifactMetadataList = append(artifactMetadataList, *artifactMetadata) } return artifactMetadataList } -func mapToArtifactMetadata(artifact types.ArtifactMetadata) *artifactapi.ArtifactMetadata { +func GetRegistryArtifactMetadata(artifacts []types.ArtifactMetadata) []artifactapi.RegistryArtifactMetadata { + artifactMetadataList := make([]artifactapi.RegistryArtifactMetadata, 0, len(artifacts)) + for _, artifact := range artifacts { + artifactMetadata := mapToRegistryArtifactMetadata(artifact) + artifactMetadataList = append(artifactMetadataList, *artifactMetadata) + } + return artifactMetadataList +} + +func mapToArtifactMetadata( + artifact types.ArtifactMetadata, + rootIdentifier string, + registryURL string, +) *artifactapi.ArtifactMetadata { lastModified := GetTimeInMs(artifact.ModifiedAt) packageType := artifact.PackageType + pullCommand := GetPullCommand(rootIdentifier, artifact.RepoName, artifact.Name, artifact.Version, + string(packageType), registryURL) return &artifactapi.ArtifactMetadata{ + RegistryIdentifier: artifact.RepoName, + Name: artifact.Name, + Version: &artifact.Version, + Labels: &artifact.Labels, + LastModified: &lastModified, + PackageType: &packageType, + DownloadsCount: &artifact.DownloadCount, + PullCommand: &pullCommand, + } +} + +func mapToRegistryArtifactMetadata(artifact types.ArtifactMetadata) *artifactapi.RegistryArtifactMetadata { + lastModified := GetTimeInMs(artifact.ModifiedAt) + packageType := artifact.PackageType + return &artifactapi.RegistryArtifactMetadata{ RegistryIdentifier: artifact.RepoName, Name: artifact.Name, LatestVersion: artifact.LatestVersion, @@ -103,8 +137,15 @@ func GetAllArtifactResponse( count int64, pageNumber int64, pageSize int, + rootIdentifier string, + registryURL string, ) *artifactapi.ListArtifactResponseJSONResponse { - artifactMetadataList := GetArtifactMetadata(artifacts) + var artifactMetadataList []artifactapi.ArtifactMetadata + if artifacts == nil { + artifactMetadataList = make([]artifactapi.ArtifactMetadata, 0) + } else { + artifactMetadataList = GetArtifactMetadata(*artifacts, rootIdentifier, registryURL) + } pageCount := GetPageCount(count, pageSize) listArtifact := &artifactapi.ListArtifact{ ItemCount: &count, @@ -120,6 +161,33 @@ func GetAllArtifactResponse( return response } +func GetAllArtifactByRegistryResponse( + artifacts *[]types.ArtifactMetadata, + count int64, + pageNumber int64, + pageSize int, +) *artifactapi.ListRegistryArtifactResponseJSONResponse { + var artifactMetadataList []artifactapi.RegistryArtifactMetadata + if artifacts == nil { + artifactMetadataList = make([]artifactapi.RegistryArtifactMetadata, 0) + } else { + artifactMetadataList = GetRegistryArtifactMetadata(*artifacts) + } + pageCount := GetPageCount(count, pageSize) + listArtifact := &artifactapi.ListRegistryArtifact{ + ItemCount: &count, + PageCount: &pageCount, + PageIndex: &pageNumber, + PageSize: &pageSize, + Artifacts: artifactMetadataList, + } + response := &artifactapi.ListRegistryArtifactResponseJSONResponse{ + Data: *listArtifact, + Status: artifactapi.StatusSUCCESS, + } + return response +} + func GetAllArtifactLabelsResponse( artifactLabels *[]string, count int64, diff --git a/registry/app/api/controller/metadata/base.go b/registry/app/api/controller/metadata/base.go index 72c15e424..fa9ddc476 100644 --- a/registry/app/api/controller/metadata/base.go +++ b/registry/app/api/controller/metadata/base.go @@ -57,6 +57,21 @@ type RegistryRequestInfo struct { pageNumber int64 searchTerm string labels []string + registryIDs []string +} + +type RegistryRequestParams struct { + packageTypesParam *api.PackageTypeParam + page *api.PageNumber + size *api.PageSize + search *api.SearchTerm + resource string + parentRef string + regRef string + labelsParam *api.LabelsParam + sortOrder *api.SortOrder + sortField *api.SortField + registryIDsParam *api.RegistryIdentifierParam } // GetRegistryRequestBaseInfo returns the base info for the registry request @@ -117,50 +132,45 @@ func (c *APIController) GetRegistryRequestBaseInfo( func (c *APIController) GetRegistryRequestInfo( ctx context.Context, - packageTypesParam *api.PackageTypeParam, - page *api.PageNumber, - size *api.PageSize, - search *api.SearchTerm, - resource string, - parentRef string, - regRef string, - labelsParam *api.LabelsParam, - sortOrder *api.SortOrder, - sortField *api.SortField, + registryRequestParams RegistryRequestParams, ) (*RegistryRequestInfo, error) { packageTypes := []string{} - if packageTypesParam != nil { - packageTypes = *packageTypesParam + if registryRequestParams.packageTypesParam != nil { + packageTypes = *registryRequestParams.packageTypesParam + } + registryIDs := []string{} + if registryRequestParams.registryIDsParam != nil { + registryIDs = *registryRequestParams.registryIDsParam } sortByField := "" sortByOrder := "" - if sortOrder != nil { - sortByOrder = string(*sortOrder) + if registryRequestParams.sortOrder != nil { + sortByOrder = string(*registryRequestParams.sortOrder) } - if sortField != nil { - sortByField = string(*sortField) + if registryRequestParams.sortField != nil { + sortByField = string(*registryRequestParams.sortField) } labels := []string{} - if labelsParam != nil { - labels = *labelsParam + if registryRequestParams.labelsParam != nil { + labels = *registryRequestParams.labelsParam } - sortByField = GetSortByField(sortByField, resource) + sortByField = GetSortByField(sortByField, registryRequestParams.resource) sortByOrder = GetSortByOrder(sortByOrder) - offset := GetOffset(size, page) - limit := GetPageLimit(size) - pageNumber := GetPageNumber(page) + offset := GetOffset(registryRequestParams.size, registryRequestParams.page) + limit := GetPageLimit(registryRequestParams.size) + pageNumber := GetPageNumber(registryRequestParams.page) searchTerm := "" - if search != nil { - searchTerm = string(*search) + if registryRequestParams.search != nil { + searchTerm = string(*registryRequestParams.search) } - baseInfo, err := c.GetRegistryRequestBaseInfo(ctx, parentRef, regRef) + baseInfo, err := c.GetRegistryRequestBaseInfo(ctx, registryRequestParams.parentRef, registryRequestParams.regRef) if err != nil { return nil, err } @@ -175,6 +185,7 @@ func (c *APIController) GetRegistryRequestInfo( pageNumber: pageNumber, searchTerm: searchTerm, labels: labels, + registryIDs: registryIDs, }, nil } diff --git a/registry/app/api/controller/metadata/get_artifacts.go b/registry/app/api/controller/metadata/get_artifacts.go index 772cb7447..9db45a6d4 100644 --- a/registry/app/api/controller/metadata/get_artifacts.go +++ b/registry/app/api/controller/metadata/get_artifacts.go @@ -21,7 +21,6 @@ import ( apiauth "github.com/harness/gitness/app/api/auth" "github.com/harness/gitness/app/api/request" "github.com/harness/gitness/registry/app/api/openapi/contracts/artifact" - "github.com/harness/gitness/registry/types" "github.com/harness/gitness/types/enum" ) @@ -29,20 +28,21 @@ func (c *APIController) GetAllArtifacts( ctx context.Context, r artifact.GetAllArtifactsRequestObject, ) (artifact.GetAllArtifactsResponseObject, error) { - ref := "" - if r.Params.RegIdentifier != nil { - ref2, err2 := GetRegRef(string(r.SpaceRef), string(*r.Params.RegIdentifier)) - if err2 != nil { - return c.getAllArtifacts400JsonResponse(err2) - } - ref = ref2 + registryRequestParams := &RegistryRequestParams{ + packageTypesParam: nil, + page: r.Params.Page, + size: r.Params.Size, + search: r.Params.SearchTerm, + resource: ArtifactResource, + parentRef: string(r.SpaceRef), + regRef: "", + labelsParam: nil, + sortOrder: r.Params.SortOrder, + sortField: r.Params.SortField, + registryIDsParam: r.Params.RegIdentifier, } - regInfo, err := c.GetRegistryRequestInfo( - ctx, r.Params.PackageType, r.Params.Page, r.Params.Size, - r.Params.SearchTerm, ArtifactResource, string(r.SpaceRef), ref, r.Params.Label, - r.Params.SortOrder, r.Params.SortField, - ) + regInfo, err := c.GetRegistryRequestInfo(ctx, *registryRequestParams) if err != nil { return c.getAllArtifacts400JsonResponse(err) } @@ -70,28 +70,16 @@ func (c *APIController) GetAllArtifacts( ), }, nil } - - var artifacts *[]types.ArtifactMetadata - var count int64 - if len(regInfo.RegistryIdentifier) == 0 { - artifacts, err = c.TagStore.GetAllArtifactsByParentID( - ctx, regInfo.parentID, ®Info.packageTypes, - regInfo.sortByField, regInfo.sortByOrder, regInfo.limit, regInfo.offset, regInfo.searchTerm, regInfo.labels, - ) - count, _ = c.TagStore.CountAllArtifactsByParentID( - ctx, regInfo.parentID, ®Info.packageTypes, - regInfo.searchTerm, regInfo.labels, - ) - } else { - artifacts, err = c.TagStore.GetAllArtifactsByRepo( - ctx, regInfo.parentID, regInfo.RegistryIdentifier, - regInfo.sortByField, regInfo.sortByOrder, regInfo.limit, regInfo.offset, regInfo.searchTerm, regInfo.labels, - ) - count, _ = c.TagStore.CountAllArtifactsByRepo( - ctx, regInfo.parentID, regInfo.RegistryIdentifier, - regInfo.searchTerm, regInfo.labels, - ) + latestVersion := false + if r.Params.LatestVersion != nil { + latestVersion = bool(*r.Params.LatestVersion) } + artifacts, err := c.TagStore.GetAllArtifactsByParentID( + ctx, regInfo.parentID, ®Info.registryIDs, + regInfo.sortByField, regInfo.sortByOrder, regInfo.limit, regInfo.offset, regInfo.searchTerm, latestVersion) + count, _ := c.TagStore.CountAllArtifactsByParentID( + ctx, regInfo.parentID, ®Info.registryIDs, + regInfo.searchTerm, latestVersion) if err != nil { return artifact.GetAllArtifacts500JSONResponse{ InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse( @@ -100,7 +88,8 @@ func (c *APIController) GetAllArtifacts( }, nil } return artifact.GetAllArtifacts200JSONResponse{ - ListArtifactResponseJSONResponse: *GetAllArtifactResponse(artifacts, count, regInfo.pageNumber, regInfo.limit), + ListArtifactResponseJSONResponse: *GetAllArtifactResponse(artifacts, count, regInfo.pageNumber, regInfo.limit, + regInfo.RootIdentifier, c.URLProvider.RegistryURL()), }, nil } diff --git a/registry/app/api/controller/metadata/get_artifacts_labels.go b/registry/app/api/controller/metadata/get_artifacts_labels.go index 9e31b14c8..a69fbaa4e 100644 --- a/registry/app/api/controller/metadata/get_artifacts_labels.go +++ b/registry/app/api/controller/metadata/get_artifacts_labels.go @@ -28,11 +28,21 @@ func (c *APIController) ListArtifactLabels( ctx context.Context, r artifact.ListArtifactLabelsRequestObject, ) (artifact.ListArtifactLabelsResponseObject, error) { + registryRequestParams := &RegistryRequestParams{ + packageTypesParam: nil, + page: r.Params.Page, + size: r.Params.Size, + search: r.Params.SearchTerm, + resource: ArtifactResource, + parentRef: "", + regRef: string(r.RegistryRef), + labelsParam: nil, + sortOrder: nil, + sortField: nil, + registryIDsParam: nil, + } regInfo, _ := c.GetRegistryRequestInfo( - ctx, nil, r.Params.Page, r.Params.Size, - r.Params.SearchTerm, ArtifactResource, "", string(r.RegistryRef), - nil, nil, nil, - ) + ctx, *registryRequestParams) space, err := c.SpaceStore.FindByRef(ctx, regInfo.ParentRef) if err != nil { diff --git a/registry/app/api/controller/metadata/get_artifacts_versions.go b/registry/app/api/controller/metadata/get_artifacts_versions.go index 9be7348c3..20aef52d3 100644 --- a/registry/app/api/controller/metadata/get_artifacts_versions.go +++ b/registry/app/api/controller/metadata/get_artifacts_versions.go @@ -36,11 +36,20 @@ func (c *APIController) GetAllArtifactVersions( ctx context.Context, r artifact.GetAllArtifactVersionsRequestObject, ) (artifact.GetAllArtifactVersionsResponseObject, error) { - regInfo, _ := c.GetRegistryRequestInfo( - ctx, nil, r.Params.Page, r.Params.Size, - r.Params.SearchTerm, ArtifactVersionResource, "", string(r.RegistryRef), - nil, r.Params.SortOrder, r.Params.SortField, - ) + registryRequestParams := &RegistryRequestParams{ + packageTypesParam: nil, + page: r.Params.Page, + size: r.Params.Size, + search: r.Params.SearchTerm, + resource: ArtifactVersionResource, + parentRef: "", + regRef: string(r.RegistryRef), + labelsParam: nil, + sortOrder: r.Params.SortOrder, + sortField: r.Params.SortField, + registryIDsParam: nil, + } + regInfo, _ := c.GetRegistryRequestInfo(ctx, *registryRequestParams) space, err := c.SpaceStore.FindByRef(ctx, regInfo.ParentRef) if err != nil { diff --git a/registry/app/api/controller/metadata/get_registries.go b/registry/app/api/controller/metadata/get_registries.go index 61d6d6b58..275fcf36b 100644 --- a/registry/app/api/controller/metadata/get_registries.go +++ b/registry/app/api/controller/metadata/get_registries.go @@ -32,11 +32,20 @@ func (c *APIController) GetAllRegistries( ctx context.Context, r artifact.GetAllRegistriesRequestObject, ) (artifact.GetAllRegistriesResponseObject, error) { - regInfo, _ := c.GetRegistryRequestInfo( - ctx, r.Params.PackageType, r.Params.Page, r.Params.Size, - r.Params.SearchTerm, RepositoryResource, string(r.SpaceRef), "", nil, - r.Params.SortOrder, r.Params.SortField, - ) + registryRequestParams := &RegistryRequestParams{ + packageTypesParam: nil, + page: r.Params.Page, + size: r.Params.Size, + search: r.Params.SearchTerm, + resource: RepositoryResource, + parentRef: string(r.SpaceRef), + regRef: "", + labelsParam: nil, + sortOrder: r.Params.SortOrder, + sortField: r.Params.SortField, + registryIDsParam: nil, + } + regInfo, _ := c.GetRegistryRequestInfo(ctx, *registryRequestParams) space, err := c.SpaceStore.FindByRef(ctx, regInfo.ParentRef) if err != nil { diff --git a/registry/app/api/controller/metadata/get_registry_artifacts.go b/registry/app/api/controller/metadata/get_registry_artifacts.go new file mode 100644 index 000000000..bb9e5c3aa --- /dev/null +++ b/registry/app/api/controller/metadata/get_registry_artifacts.go @@ -0,0 +1,102 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metadata + +import ( + "context" + "net/http" + + apiauth "github.com/harness/gitness/app/api/auth" + "github.com/harness/gitness/app/api/request" + "github.com/harness/gitness/registry/app/api/openapi/contracts/artifact" + "github.com/harness/gitness/types/enum" +) + +func (c *APIController) GetAllArtifactsByRegistry( + ctx context.Context, + r artifact.GetAllArtifactsByRegistryRequestObject, +) (artifact.GetAllArtifactsByRegistryResponseObject, error) { + registryRequestParams := &RegistryRequestParams{ + packageTypesParam: nil, + page: r.Params.Page, + size: r.Params.Size, + search: r.Params.SearchTerm, + resource: ArtifactResource, + parentRef: "", + regRef: string(r.RegistryRef), + labelsParam: nil, + sortOrder: r.Params.SortOrder, + sortField: r.Params.SortField, + registryIDsParam: nil, + } + regInfo, err := c.GetRegistryRequestInfo(ctx, *registryRequestParams) + if err != nil { + return c.getAllArtifactsByRegistry400JsonResponse(err) + } + + space, err := c.SpaceStore.FindByRef(ctx, regInfo.ParentRef) + if err != nil { + return artifact.GetAllArtifactsByRegistry400JSONResponse{ + BadRequestJSONResponse: artifact.BadRequestJSONResponse( + *GetErrorResponse(http.StatusBadRequest, err.Error()), + ), + }, nil + } + + session, _ := request.AuthSessionFrom(ctx) + permissionChecks := GetPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView) + if err = apiauth.CheckRegistry( + ctx, + c.Authorizer, + session, + permissionChecks..., + ); err != nil { + return artifact.GetAllArtifactsByRegistry403JSONResponse{ + UnauthorizedJSONResponse: artifact.UnauthorizedJSONResponse( + *GetErrorResponse(http.StatusForbidden, err.Error()), + ), + }, nil + } + artifacts, err := c.TagStore.GetAllArtifactsByRepo( + ctx, regInfo.parentID, regInfo.RegistryIdentifier, + regInfo.sortByField, regInfo.sortByOrder, regInfo.limit, regInfo.offset, regInfo.searchTerm, regInfo.labels, + ) + count, _ := c.TagStore.CountAllArtifactsByRepo( + ctx, regInfo.parentID, regInfo.RegistryIdentifier, + regInfo.searchTerm, regInfo.labels, + ) + if err != nil { + return artifact.GetAllArtifactsByRegistry500JSONResponse{ + InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse( + *GetErrorResponse(http.StatusInternalServerError, err.Error()), + ), + }, nil + } + return artifact.GetAllArtifactsByRegistry200JSONResponse{ + ListRegistryArtifactResponseJSONResponse: *GetAllArtifactByRegistryResponse( + artifacts, count, regInfo.pageNumber, regInfo.limit, + ), + }, nil +} + +func (c *APIController) getAllArtifactsByRegistry400JsonResponse(err error) ( + artifact.GetAllArtifactsByRegistryResponseObject, error, +) { + return artifact.GetAllArtifactsByRegistry400JSONResponse{ + BadRequestJSONResponse: artifact.BadRequestJSONResponse( + *GetErrorResponse(http.StatusBadRequest, err.Error()), + ), + }, nil +} diff --git a/registry/app/api/controller/metadata/update_artifact.go b/registry/app/api/controller/metadata/update_artifact.go index 90e9da14a..70bea9056 100644 --- a/registry/app/api/controller/metadata/update_artifact.go +++ b/registry/app/api/controller/metadata/update_artifact.go @@ -29,10 +29,20 @@ func (c *APIController) UpdateArtifactLabels( ctx context.Context, r artifact.UpdateArtifactLabelsRequestObject, ) (artifact.UpdateArtifactLabelsResponseObject, error) { - regInfo, _ := c.GetRegistryRequestInfo( - ctx, nil, nil, nil, nil, - ArtifactVersionResource, "", string(r.RegistryRef), nil, nil, nil, - ) + registryRequestParams := &RegistryRequestParams{ + packageTypesParam: nil, + page: nil, + size: nil, + search: nil, + resource: ArtifactVersionResource, + parentRef: "", + regRef: string(r.RegistryRef), + labelsParam: nil, + sortOrder: nil, + sortField: nil, + registryIDsParam: nil, + } + regInfo, _ := c.GetRegistryRequestInfo(ctx, *registryRequestParams) space, err := c.SpaceStore.FindByRef(ctx, regInfo.ParentRef) if err != nil { diff --git a/registry/app/api/controller/metadata/utils.go b/registry/app/api/controller/metadata/utils.go index 2e145cde2..eaf54e7d1 100644 --- a/registry/app/api/controller/metadata/utils.go +++ b/registry/app/api/controller/metadata/utils.go @@ -400,7 +400,7 @@ func GetPermissionChecks( ) []types.PermissionCheck { var permissionChecks []types.PermissionCheck permissionCheck := &types.PermissionCheck{ - Scope: types.Scope{SpacePath: space.Identifier}, + Scope: types.Scope{SpacePath: space.Path}, Resource: types.Resource{Type: enum.ResourceTypeRegistry, Identifier: registryIdentifier}, Permission: permission, } diff --git a/registry/app/api/handler/oci/get_token.go b/registry/app/api/handler/oci/get_token.go index c8bfe7da3..4bba40f8c 100644 --- a/registry/app/api/handler/oci/get_token.go +++ b/registry/app/api/handler/oci/get_token.go @@ -16,6 +16,7 @@ package oci import ( "context" + "encoding/json" "fmt" "net/http" "net/url" @@ -34,6 +35,10 @@ import ( "github.com/rs/zerolog/log" ) +type TokenResponseOCI struct { + Token string `json:"token"` +} + func (h *Handler) GetToken(w http.ResponseWriter, r *http.Request) { ctx := r.Context() session, ok := request.AuthSessionFrom(ctx) @@ -79,8 +84,12 @@ func (h *Handler) GetToken(w http.ResponseWriter, r *http.Request) { } if jwtToken != "" { w.WriteHeader(http.StatusOK) - _, err := w.Write([]byte(fmt.Sprintf("{\"token\":\"%s\"}", jwtToken))) - if err != nil { + enc := json.NewEncoder(w) + if err := enc.Encode( + TokenResponseOCI{ + Token: jwtToken, + }, + ); err != nil { log.Error().Msgf("failed to write token response: %v", err) } return diff --git a/registry/app/api/openapi/api.yaml b/registry/app/api/openapi/api.yaml index c246110bf..325037457 100644 --- a/registry/app/api/openapi/api.yaml +++ b/registry/app/api/openapi/api.yaml @@ -173,17 +173,44 @@ paths: - Spaces parameters: - $ref: "#/components/parameters/spaceRefPathParam" - - $ref: "#/components/parameters/LabelsParam" - - $ref: "#/components/parameters/packageTypeParam" - $ref: "#/components/parameters/RegistryIdentifierParam" - $ref: "#/components/parameters/pageNumber" - $ref: "#/components/parameters/pageSize" - $ref: "#/components/parameters/sortOrder" - $ref: "#/components/parameters/sortField" - $ref: "#/components/parameters/searchTerm" + - $ref: "#/components/parameters/latestVersion" + responses: + 200: + $ref: "#/components/responses/ListArtifactResponse" + 400: + $ref: "#/components/responses/BadRequest" + 401: + $ref: "#/components/responses/Unauthenticated" + 403: + $ref: "#/components/responses/Unauthorized" + 404: + $ref: "#/components/responses/NotFound" + 500: + $ref: "#/components/responses/InternalServerError" + /registry/{registry_ref}/artifacts: + get: + summary: List Artifacts for Registry + description: Lists all the Artifacts for Registry + operationId: GetAllArtifactsByRegistry + tags: + - Registries + parameters: + - $ref: "#/components/parameters/registryRefPathParam" + - $ref: "#/components/parameters/LabelsParam" + - $ref: "#/components/parameters/pageNumber" + - $ref: "#/components/parameters/pageSize" + - $ref: "#/components/parameters/sortOrder" + - $ref: "#/components/parameters/sortField" + - $ref: "#/components/parameters/searchTerm" responses: 200: - $ref: "#/components/responses/ListArtifactResponse" + $ref: "#/components/responses/ListRegistryArtifactResponse" 400: $ref: "#/components/responses/BadRequest" 401: @@ -764,6 +791,20 @@ components: required: - status - data + ListRegistryArtifactResponse: + description: response for list artifact + content: + application/json: + schema: + type: object + properties: + status: + $ref: "#/components/schemas/Status" + data: + $ref: "#/components/schemas/ListRegistryArtifact" + required: + - status + - data ListArtifactVersionResponse: description: response for list versions of artifact content: @@ -896,6 +937,36 @@ components: $ref: "#/components/schemas/ArtifactMetadata" required: - artifacts + ListRegistryArtifact: + type: object + description: A list of Artifacts + properties: + pageCount: + type: integer + format: int64 + description: The total number of pages + example: 100 + itemCount: + type: integer + format: int64 + description: The total number of items + example: 1 + pageSize: + type: integer + description: The number of items per page + example: 1 + pageIndex: + type: integer + format: int64 + description: The current page + example: 0 + artifacts: + type: array + description: A list of Artifact + items: + $ref: "#/components/schemas/RegistryArtifactMetadata" + required: + - artifacts ListArtifactVersion: type: object description: A list of Artifact versions @@ -1029,6 +1100,36 @@ components: type: $ref: "#/components/schemas/ClientSetupStepType" ArtifactMetadata: + type: object + description: Artifact Metadata + properties: + name: + type: string + version: + type: string + registryIdentifier: + type: string + registryPath: + type: string + labels: + type: array + items: + type: string + downloadsCount: + type: integer + format: int64 + lastModified: + type: string + pullCommand: + type: string + packageType: + $ref: "#/components/schemas/PackageType" + required: + - name + - registryIdentifier + - latestVersion + - registryPath + RegistryArtifactMetadata: type: object description: Artifact Metadata properties: @@ -1397,7 +1498,7 @@ components: description: "ClientSetupStepType type" enum: - Static - - GenerateToken + - GenerateToken Error: type: object properties: @@ -1503,7 +1604,9 @@ components: required: false description: Registry Identifier schema: - type: string + type: array + items: + type: string spaceRefPathParam: name: space_ref in: path @@ -1601,6 +1704,13 @@ components: description: sortField schema: type: string + latestVersion: + name: latest_version + in: query + required: false + description: Latest Version Filter. + schema: + type: boolean fromDateParam: name: from in: query @@ -1614,4 +1724,4 @@ components: required: false description: Date. Format - MM/DD/YYYY schema: - type: string \ No newline at end of file + type: string \ No newline at end of file diff --git a/registry/app/api/openapi/contracts/artifact/services.gen.go b/registry/app/api/openapi/contracts/artifact/services.gen.go index f13910304..f6c8f6660 100644 --- a/registry/app/api/openapi/contracts/artifact/services.gen.go +++ b/registry/app/api/openapi/contracts/artifact/services.gen.go @@ -74,6 +74,9 @@ type ServerInterface interface { // List Artifact Versions // (GET /registry/{registry_ref}/artifact/{artifact}/versions) GetAllArtifactVersions(w http.ResponseWriter, r *http.Request, registryRef RegistryRefPathParam, artifact ArtifactPathParam, params GetAllArtifactVersionsParams) + // List Artifacts for Registry + // (GET /registry/{registry_ref}/artifacts) + GetAllArtifactsByRegistry(w http.ResponseWriter, r *http.Request, registryRef RegistryRefPathParam, params GetAllArtifactsByRegistryParams) // Returns CLI Client Setup Details // (GET /registry/{registry_ref}/client-setup-details) GetClientSetupDetails(w http.ResponseWriter, r *http.Request, registryRef RegistryRefPathParam, params GetClientSetupDetailsParams) @@ -194,6 +197,12 @@ func (_ Unimplemented) GetAllArtifactVersions(w http.ResponseWriter, r *http.Req w.WriteHeader(http.StatusNotImplemented) } +// List Artifacts for Registry +// (GET /registry/{registry_ref}/artifacts) +func (_ Unimplemented) GetAllArtifactsByRegistry(w http.ResponseWriter, r *http.Request, registryRef RegistryRefPathParam, params GetAllArtifactsByRegistryParams) { + w.WriteHeader(http.StatusNotImplemented) +} + // Returns CLI Client Setup Details // (GET /registry/{registry_ref}/client-setup-details) func (_ Unimplemented) GetClientSetupDetails(w http.ResponseWriter, r *http.Request, registryRef RegistryRefPathParam, params GetClientSetupDetailsParams) { @@ -995,6 +1004,83 @@ func (siw *ServerInterfaceWrapper) GetAllArtifactVersions(w http.ResponseWriter, handler.ServeHTTP(w, r.WithContext(ctx)) } +// GetAllArtifactsByRegistry operation middleware +func (siw *ServerInterfaceWrapper) GetAllArtifactsByRegistry(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "registry_ref" ------------- + var registryRef RegistryRefPathParam + + err = runtime.BindStyledParameterWithOptions("simple", "registry_ref", chi.URLParam(r, "registry_ref"), ®istryRef, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "registry_ref", Err: err}) + return + } + + // Parameter object where we will unmarshal all parameters from the context + var params GetAllArtifactsByRegistryParams + + // ------------- Optional query parameter "label" ------------- + + err = runtime.BindQueryParameter("form", true, false, "label", r.URL.Query(), ¶ms.Label) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "label", Err: err}) + return + } + + // ------------- Optional query parameter "page" ------------- + + err = runtime.BindQueryParameter("form", true, false, "page", r.URL.Query(), ¶ms.Page) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "page", Err: err}) + return + } + + // ------------- Optional query parameter "size" ------------- + + err = runtime.BindQueryParameter("form", true, false, "size", r.URL.Query(), ¶ms.Size) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "size", Err: err}) + return + } + + // ------------- Optional query parameter "sort_order" ------------- + + err = runtime.BindQueryParameter("form", true, false, "sort_order", r.URL.Query(), ¶ms.SortOrder) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "sort_order", Err: err}) + return + } + + // ------------- Optional query parameter "sort_field" ------------- + + err = runtime.BindQueryParameter("form", true, false, "sort_field", r.URL.Query(), ¶ms.SortField) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "sort_field", Err: err}) + return + } + + // ------------- Optional query parameter "search_term" ------------- + + err = runtime.BindQueryParameter("form", true, false, "search_term", r.URL.Query(), ¶ms.SearchTerm) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "search_term", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetAllArtifactsByRegistry(w, r, registryRef, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + // GetClientSetupDetails operation middleware func (siw *ServerInterfaceWrapper) GetClientSetupDetails(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -1103,22 +1189,6 @@ func (siw *ServerInterfaceWrapper) GetAllArtifacts(w http.ResponseWriter, r *htt // Parameter object where we will unmarshal all parameters from the context var params GetAllArtifactsParams - // ------------- Optional query parameter "label" ------------- - - err = runtime.BindQueryParameter("form", true, false, "label", r.URL.Query(), ¶ms.Label) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "label", Err: err}) - return - } - - // ------------- Optional query parameter "package_type" ------------- - - err = runtime.BindQueryParameter("form", true, false, "package_type", r.URL.Query(), ¶ms.PackageType) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "package_type", Err: err}) - return - } - // ------------- Optional query parameter "reg_identifier" ------------- err = runtime.BindQueryParameter("form", true, false, "reg_identifier", r.URL.Query(), ¶ms.RegIdentifier) @@ -1167,6 +1237,14 @@ func (siw *ServerInterfaceWrapper) GetAllArtifacts(w http.ResponseWriter, r *htt return } + // ------------- Optional query parameter "latest_version" ------------- + + err = runtime.BindQueryParameter("form", true, false, "latest_version", r.URL.Query(), ¶ms.LatestVersion) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "latest_version", Err: err}) + return + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetAllArtifacts(w, r, spaceRef, params) })) @@ -1427,6 +1505,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/registry/{registry_ref}/artifact/{artifact}/versions", wrapper.GetAllArtifactVersions) }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/registry/{registry_ref}/artifacts", wrapper.GetAllArtifactsByRegistry) + }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/registry/{registry_ref}/client-setup-details", wrapper.GetClientSetupDetails) }) @@ -1559,6 +1640,14 @@ type ListArtifactVersionResponseJSONResponse struct { Status Status `json:"status"` } +type ListRegistryArtifactResponseJSONResponse struct { + // Data A list of Artifacts + Data ListRegistryArtifact `json:"data"` + + // Status Indicates if the request was successful or not + Status Status `json:"status"` +} + type ListRegistryResponseJSONResponse struct { // Data A list of Harness Artifact Registries Data ListRegistry `json:"data"` @@ -2719,6 +2808,73 @@ func (response GetAllArtifactVersions500JSONResponse) VisitGetAllArtifactVersion return json.NewEncoder(w).Encode(response) } +type GetAllArtifactsByRegistryRequestObject struct { + RegistryRef RegistryRefPathParam `json:"registry_ref"` + Params GetAllArtifactsByRegistryParams +} + +type GetAllArtifactsByRegistryResponseObject interface { + VisitGetAllArtifactsByRegistryResponse(w http.ResponseWriter) error +} + +type GetAllArtifactsByRegistry200JSONResponse struct { + ListRegistryArtifactResponseJSONResponse +} + +func (response GetAllArtifactsByRegistry200JSONResponse) VisitGetAllArtifactsByRegistryResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type GetAllArtifactsByRegistry400JSONResponse struct{ BadRequestJSONResponse } + +func (response GetAllArtifactsByRegistry400JSONResponse) VisitGetAllArtifactsByRegistryResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type GetAllArtifactsByRegistry401JSONResponse struct{ UnauthenticatedJSONResponse } + +func (response GetAllArtifactsByRegistry401JSONResponse) VisitGetAllArtifactsByRegistryResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(401) + + return json.NewEncoder(w).Encode(response) +} + +type GetAllArtifactsByRegistry403JSONResponse struct{ UnauthorizedJSONResponse } + +func (response GetAllArtifactsByRegistry403JSONResponse) VisitGetAllArtifactsByRegistryResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(403) + + return json.NewEncoder(w).Encode(response) +} + +type GetAllArtifactsByRegistry404JSONResponse struct{ NotFoundJSONResponse } + +func (response GetAllArtifactsByRegistry404JSONResponse) VisitGetAllArtifactsByRegistryResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + +type GetAllArtifactsByRegistry500JSONResponse struct { + InternalServerErrorJSONResponse +} + +func (response GetAllArtifactsByRegistry500JSONResponse) VisitGetAllArtifactsByRegistryResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + type GetClientSetupDetailsRequestObject struct { RegistryRef RegistryRefPathParam `json:"registry_ref"` Params GetClientSetupDetailsParams @@ -3040,6 +3196,9 @@ type StrictServerInterface interface { // List Artifact Versions // (GET /registry/{registry_ref}/artifact/{artifact}/versions) GetAllArtifactVersions(ctx context.Context, request GetAllArtifactVersionsRequestObject) (GetAllArtifactVersionsResponseObject, error) + // List Artifacts for Registry + // (GET /registry/{registry_ref}/artifacts) + GetAllArtifactsByRegistry(ctx context.Context, request GetAllArtifactsByRegistryRequestObject) (GetAllArtifactsByRegistryResponseObject, error) // Returns CLI Client Setup Details // (GET /registry/{registry_ref}/client-setup-details) GetClientSetupDetails(ctx context.Context, request GetClientSetupDetailsRequestObject) (GetClientSetupDetailsResponseObject, error) @@ -3571,6 +3730,33 @@ func (sh *strictHandler) GetAllArtifactVersions(w http.ResponseWriter, r *http.R } } +// GetAllArtifactsByRegistry operation middleware +func (sh *strictHandler) GetAllArtifactsByRegistry(w http.ResponseWriter, r *http.Request, registryRef RegistryRefPathParam, params GetAllArtifactsByRegistryParams) { + var request GetAllArtifactsByRegistryRequestObject + + request.RegistryRef = registryRef + request.Params = params + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.GetAllArtifactsByRegistry(ctx, request.(GetAllArtifactsByRegistryRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetAllArtifactsByRegistry") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(GetAllArtifactsByRegistryResponseObject); ok { + if err := validResponse.VisitGetAllArtifactsByRegistryResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + // GetClientSetupDetails operation middleware func (sh *strictHandler) GetClientSetupDetails(w http.ResponseWriter, r *http.Request, registryRef RegistryRefPathParam, params GetClientSetupDetailsParams) { var request GetClientSetupDetailsRequestObject @@ -3682,69 +3868,70 @@ func (sh *strictHandler) GetAllRegistries(w http.ResponseWriter, r *http.Request // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "y6/L/wUAAP//B0v/XHmOAAA=", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/registry/app/api/openapi/contracts/artifact/types.gen.go b/registry/app/api/openapi/contracts/artifact/types.gen.go index 296d259f1..b394e10a3 100644 --- a/registry/app/api/openapi/contracts/artifact/types.gen.go +++ b/registry/app/api/openapi/contracts/artifact/types.gen.go @@ -75,13 +75,14 @@ type ArtifactMetadata struct { DownloadsCount *int64 `json:"downloadsCount,omitempty"` Labels *[]string `json:"labels,omitempty"` LastModified *string `json:"lastModified,omitempty"` - LatestVersion string `json:"latestVersion"` Name string `json:"name"` // PackageType refers to package PackageType *PackageType `json:"packageType,omitempty"` + PullCommand *string `json:"pullCommand,omitempty"` RegistryIdentifier string `json:"registryIdentifier"` RegistryPath string `json:"registryPath"` + Version *string `json:"version,omitempty"` } // ArtifactStats Harness Artifact Stats @@ -321,6 +322,24 @@ type ListRegistry struct { Registries []RegistryMetadata `json:"registries"` } +// ListRegistryArtifact A list of Artifacts +type ListRegistryArtifact struct { + // Artifacts A list of Artifact + Artifacts []RegistryArtifactMetadata `json:"artifacts"` + + // ItemCount The total number of items + ItemCount *int64 `json:"itemCount,omitempty"` + + // PageCount The total number of pages + PageCount *int64 `json:"pageCount,omitempty"` + + // PageIndex The current page + PageIndex *int64 `json:"pageIndex,omitempty"` + + // PageSize The number of items per page + PageSize *int `json:"pageSize,omitempty"` +} + // PackageType refers to package type PackageType string @@ -343,6 +362,20 @@ type Registry struct { Url string `json:"url"` } +// RegistryArtifactMetadata Artifact Metadata +type RegistryArtifactMetadata struct { + DownloadsCount *int64 `json:"downloadsCount,omitempty"` + Labels *[]string `json:"labels,omitempty"` + LastModified *string `json:"lastModified,omitempty"` + LatestVersion string `json:"latestVersion"` + Name string `json:"name"` + + // PackageType refers to package + PackageType *PackageType `json:"packageType,omitempty"` + RegistryIdentifier string `json:"registryIdentifier"` + RegistryPath string `json:"registryPath"` +} + // RegistryConfig SubConfig specific for Virtual or Upstream Registry type RegistryConfig struct { // Type refers to type of registry i.e virtual or upstream @@ -427,7 +460,7 @@ type VirtualConfig struct { type LabelsParam []string // RegistryIdentifierParam defines model for RegistryIdentifierParam. -type RegistryIdentifierParam string +type RegistryIdentifierParam []string // RegistryTypeParam defines model for RegistryTypeParam. type RegistryTypeParam string @@ -444,6 +477,9 @@ type DigestParam string // FromDateParam defines model for fromDateParam. type FromDateParam string +// LatestVersion defines model for latestVersion. +type LatestVersion bool + // PackageTypeParam defines model for packageTypeParam. type PackageTypeParam []string @@ -612,6 +648,15 @@ type ListArtifactVersionResponse struct { Status Status `json:"status"` } +// ListRegistryArtifactResponse defines model for ListRegistryArtifactResponse. +type ListRegistryArtifactResponse struct { + // Data A list of Artifacts + Data ListRegistryArtifact `json:"data"` + + // Status Indicates if the request was successful or not + Status Status `json:"status"` +} + // ListRegistryResponse defines model for ListRegistryResponse. type ListRegistryResponse struct { // Data A list of Harness Artifact Registries @@ -717,6 +762,27 @@ type GetAllArtifactVersionsParams struct { SearchTerm *SearchTerm `form:"search_term,omitempty" json:"search_term,omitempty"` } +// GetAllArtifactsByRegistryParams defines parameters for GetAllArtifactsByRegistry. +type GetAllArtifactsByRegistryParams struct { + // Label Label. + Label *LabelsParam `form:"label,omitempty" json:"label,omitempty"` + + // Page Current page number + Page *PageNumber `form:"page,omitempty" json:"page,omitempty"` + + // Size Number of items per page + Size *PageSize `form:"size,omitempty" json:"size,omitempty"` + + // SortOrder sortOrder + SortOrder *SortOrder `form:"sort_order,omitempty" json:"sort_order,omitempty"` + + // SortField sortField + SortField *SortField `form:"sort_field,omitempty" json:"sort_field,omitempty"` + + // SearchTerm search Term. + SearchTerm *SearchTerm `form:"search_term,omitempty" json:"search_term,omitempty"` +} + // GetClientSetupDetailsParams defines parameters for GetClientSetupDetails. type GetClientSetupDetailsParams struct { // Artifact Artifat @@ -737,12 +803,6 @@ type GetArtifactStatsForSpaceParams struct { // GetAllArtifactsParams defines parameters for GetAllArtifacts. type GetAllArtifactsParams struct { - // Label Label. - Label *LabelsParam `form:"label,omitempty" json:"label,omitempty"` - - // PackageType Registry Package Type - PackageType *PackageTypeParam `form:"package_type,omitempty" json:"package_type,omitempty"` - // RegIdentifier Registry Identifier RegIdentifier *RegistryIdentifierParam `form:"reg_identifier,omitempty" json:"reg_identifier,omitempty"` @@ -760,6 +820,9 @@ type GetAllArtifactsParams struct { // SearchTerm search Term. SearchTerm *SearchTerm `form:"search_term,omitempty" json:"search_term,omitempty"` + + // LatestVersion Latest Version Filter. + LatestVersion *LatestVersion `form:"latest_version,omitempty" json:"latest_version,omitempty"` } // GetAllRegistriesParams defines parameters for GetAllRegistries. diff --git a/registry/app/api/wire.go b/registry/app/api/wire.go index 27342ee56..de5d0f36c 100644 --- a/registry/app/api/wire.go +++ b/registry/app/api/wire.go @@ -30,6 +30,7 @@ import ( "github.com/harness/gitness/registry/app/pkg/docker" "github.com/harness/gitness/registry/app/store/database" "github.com/harness/gitness/registry/config" + "github.com/harness/gitness/registry/gc" "github.com/harness/gitness/types" "github.com/google/wire" @@ -64,9 +65,11 @@ func BlobStorageProvider(c *types.Config) (storagedriver.StorageDriver, error) { return d, err } -func NewHandlerProvider(controller *docker.Controller, spaceStore corestore.SpaceStore, +func NewHandlerProvider( + controller *docker.Controller, spaceStore corestore.SpaceStore, tokenStore corestore.TokenStore, userCtrl *usercontroller.Controller, authenticator authn.Authenticator, - urlProvider urlprovider.Provider, authorizer authz.Authorizer) *ocihandler.Handler { + urlProvider urlprovider.Provider, authorizer authz.Authorizer, +) *ocihandler.Handler { return ocihandler.NewHandler(controller, spaceStore, tokenStore, userCtrl, authenticator, urlProvider, authorizer) } @@ -77,6 +80,7 @@ var WireSet = wire.NewSet( pkg.WireSet, docker.WireSet, router.WireSet, + gc.WireSet, ) func Wire(_ *types.Config) (RegistryApp, error) { diff --git a/registry/app/driver/filesystem/driver.go b/registry/app/driver/filesystem/driver.go index 633bad195..38c58d031 100644 --- a/registry/app/driver/filesystem/driver.go +++ b/registry/app/driver/filesystem/driver.go @@ -177,8 +177,9 @@ func (d *driver) PutContent(ctx context.Context, subPath string, contents []byte // Reader retrieves an io.ReadCloser for the content stored at "path" with a // given byte offset. -func (d *driver) Reader(_ context.Context, path string, offset int64) (io.ReadCloser, error) { +func (d *driver) Reader(ctx context.Context, path string, offset int64) (io.ReadCloser, error) { file, err := os.OpenFile(d.fullPath(path), os.O_RDONLY, 0o644) + log.Ctx(ctx).Info().Msgf("Opening file %s %s", d.fullPath(path), d.rootDirectory) if err != nil { if os.IsNotExist(err) { return nil, storagedriver.PathNotFoundError{Path: path} diff --git a/registry/app/event/reporter.go b/registry/app/event/reporter.go new file mode 100644 index 000000000..d5ad14a71 --- /dev/null +++ b/registry/app/event/reporter.go @@ -0,0 +1,66 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package event + +import ( + "context" + "fmt" + + "github.com/harness/gitness/registry/app/api/openapi/contracts/artifact" +) + +type PackageType int32 + +type ArtifactDetails struct { + RegistryID string `json:"registry_id,omitempty"` + ImagePath string `json:"image_path,omitempty"` // format = image:tag + PackageType PackageType `json:"package_type,omitempty"` +} + +// PackageType constants using iota. +const ( + PackageTypeDOCKER = iota + PackageTypeGENERIC + PackageTypeHELM + PackageTypeMAVEN +) + +var PackageTypeValue = map[string]PackageType{ + string(artifact.PackageTypeDOCKER): PackageTypeDOCKER, + string(artifact.PackageTypeGENERIC): PackageTypeGENERIC, + string(artifact.PackageTypeHELM): PackageTypeHELM, + string(artifact.PackageTypeMAVEN): PackageTypeMAVEN, +} + +// GetPackageTypeFromString returns the PackageType constant corresponding to the given string value. +func GetPackageTypeFromString(value string) (PackageType, error) { + if val, ok := PackageTypeValue[value]; ok { + return val, nil + } + return 0, fmt.Errorf("invalid PackageType string value: %s", value) +} + +type Reporter interface { + ReportEvent( + ctx context.Context, payload interface{}, spacePath string, + ) // format of spacePath = acctID/orgID/projectID +} + +type Noop struct { +} + +func (*Noop) ReportEvent(_ context.Context, _ interface{}, _ string) { + // no implementation +} diff --git a/registry/app/pkg/docker/app.go b/registry/app/pkg/docker/app.go index 3b96d1b7d..26bf7cc06 100644 --- a/registry/app/pkg/docker/app.go +++ b/registry/app/pkg/docker/app.go @@ -55,7 +55,6 @@ func NewApp( ctx context.Context, sqlDB *sqlx.DB, storageDeleter storagedriver.StorageDeleter, blobRepo store.BlobRepository, spaceStore corestore.SpaceStore, cfg *types.Config, storageService *registrystorage.Service, - mtRepository store.MediaTypesRepository, manifestRepository store.ManifestRepository, gcService gc.Service, ) *App { app := &App{ @@ -64,7 +63,7 @@ func NewApp( storageService: storageService, } app.configureSecret(cfg) - gcService.Start(ctx, sqlDB, spaceStore, blobRepo, storageDeleter, cfg, mtRepository, manifestRepository) + gcService.Start(ctx, sqlDB, spaceStore, blobRepo, storageDeleter, cfg) return app } diff --git a/registry/app/pkg/docker/helpers.go b/registry/app/pkg/docker/helpers.go index 990eff536..3614c9fc7 100644 --- a/registry/app/pkg/docker/helpers.go +++ b/registry/app/pkg/docker/helpers.go @@ -52,7 +52,7 @@ func GetRegistryCheckAccess( for i := range reqPermissions { permissionCheck := types.PermissionCheck{ Permission: reqPermissions[i], - Scope: types.Scope{SpacePath: space.Identifier}, + Scope: types.Scope{SpacePath: space.Path}, Resource: types.Resource{ Type: enum.ResourceTypeRegistry, Identifier: registry.Name, diff --git a/registry/app/pkg/docker/manifest_service.go b/registry/app/pkg/docker/manifest_service.go index 0e76ef741..48fb6beed 100644 --- a/registry/app/pkg/docker/manifest_service.go +++ b/registry/app/pkg/docker/manifest_service.go @@ -23,6 +23,8 @@ import ( "fmt" "time" + gitnessappstore "github.com/harness/gitness/app/store" + "github.com/harness/gitness/registry/app/event" "github.com/harness/gitness/registry/app/manifest" "github.com/harness/gitness/registry/app/manifest/manifestlist" "github.com/harness/gitness/registry/app/manifest/ocischema" @@ -33,7 +35,8 @@ import ( "github.com/harness/gitness/registry/app/store/database/util" "github.com/harness/gitness/registry/gc" "github.com/harness/gitness/registry/types" - store2 "github.com/harness/gitness/store" + gitnessstore "github.com/harness/gitness/store" + db "github.com/harness/gitness/store/database" "github.com/harness/gitness/store/database/dbtx" "github.com/distribution/distribution/v3" @@ -53,8 +56,10 @@ type manifestService struct { imageDao store.ImageRepository artifactDao store.ArtifactRepository manifestRefDao store.ManifestReferenceRepository + spacePathStore gitnessappstore.SpacePathStore gcService gc.Service tx dbtx.Transactor + reporter event.Reporter } func NewManifestService( @@ -62,7 +67,7 @@ func NewManifestService( blobRepo store.BlobRepository, mtRepository store.MediaTypesRepository, tagDao store.TagRepository, imageDao store.ImageRepository, artifactDao store.ArtifactRepository, layerDao store.LayerRepository, manifestRefDao store.ManifestReferenceRepository, - tx dbtx.Transactor, gcService gc.Service, + tx dbtx.Transactor, gcService gc.Service, reporter event.Reporter, spacePathStore gitnessappstore.SpacePathStore, ) ManifestService { return &manifestService{ registryDao: registryDao, @@ -76,6 +81,8 @@ func NewManifestService( manifestRefDao: manifestRefDao, gcService: gcService, tx: tx, + reporter: reporter, + spacePathStore: spacePathStore, } } @@ -166,78 +173,154 @@ func (l *manifestService) dbTagManifest( tagName, imageName string, info pkg.RegistryInfo, ) error { - dbRepo, err := l.registryDao.GetByParentIDAndName(ctx, info.ParentID, info.RegIdentifier) + dbRegistry, err := l.registryDao.GetByParentIDAndName(ctx, info.ParentID, info.RegIdentifier) if err != nil { - return err + return formatFailedToTagErr(err) } newDigest, err := types.NewDigest(dgst) if err != nil { - return err + return formatFailedToTagErr(err) + } + dbManifest, err := l.manifestDao.FindManifestByDigest(ctx, dbRegistry.ID, info.Image, newDigest) + if errors.Is(err, gitnessstore.ErrResourceNotFound) { + return fmt.Errorf("manifest %s not found in database", dgst) } - dbManifest, err := l.manifestDao.FindManifestByDigest(ctx, dbRepo.ID, info.Image, newDigest) if err != nil { - if errors.Is(err, store2.ErrResourceNotFound) { - return fmt.Errorf("manifest %s not found in database", dgst) + return formatFailedToTagErr(err) + } + + err = l.tx.WithTx(ctx, func(ctx context.Context) error { + // Prevent long running transactions by setting an upper limit of manifestTagGCLockTimeout. If the GC is holding + // the lock of a related review record, the processing there should be fast enough to avoid this. Regardless, we + // should not let transactions open (and clients waiting) for too long. If this sensible timeout is exceeded, abort + // the tag creation and let the client retry. This will bubble up and lead to a 503 Service Unavailable response. + // Set timeout for the transaction to prevent long-running operations + ctx, cancel := context.WithTimeout(ctx, manifestTagGCLockTimeout) + defer cancel() + + // Attempt to find and lock the manifest for GC review + if err := l.lockManifestForGC(ctx, dbRegistry.ID, dbManifest.ID); err != nil { + return formatFailedToTagErr(err) } + + // Create or update artifact and tag records + if err := l.createOrUpdateArtifactAndTag(ctx, dbRegistry.ID, dbManifest.ID, imageName, tagName, dgst); err != nil { + return formatFailedToTagErr(err) + } + + return nil + }) + + if err != nil { + return formatFailedToTagErr(err) + } + spacePath, packageType, err := l.getSpacePathAndPackageType(ctx, dbRegistry) + if err == nil { + l.reportEventAsync(ctx, info.RegIdentifier, imageName, tagName, packageType, spacePath) + } else { + log.Ctx(ctx).Err(err).Msg("Failed to find spacePath, not publishing event") + } + + return nil +} + +func formatFailedToTagErr(err error) error { + return fmt.Errorf("failed to tag manifest: %w", err) +} + +// Locks the manifest for GC review. +func (l *manifestService) lockManifestForGC(ctx context.Context, repoID, manifestID int64) error { + _, err := l.gcService.ManifestFindAndLockBefore( + ctx, repoID, manifestID, + time.Now().Add(manifestTagGCReviewWindow), + ) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + // Use ProcessSQLErrorf for handling the SQL error abstraction + return db.ProcessSQLErrorf( + ctx, + err, + "failed to lock manifest for GC review [repoID: %d, manifestID: %d]", repoID, manifestID, + ) + } + return nil +} + +// Creates or updates artifact and tag records. +func (l *manifestService) createOrUpdateArtifactAndTag( + ctx context.Context, + registryID, + manifestID int64, + imageName, + tagName string, + dgst digest.Digest, +) error { + image := &types.Image{ + Name: imageName, + RegistryID: registryID, + Enabled: true, + } + + if err := l.imageDao.CreateOrUpdate(ctx, image); err != nil { return err } - // We need to find and lock a GC manifest task that is related with the manifest that we're about to tag. This - // is needed to ensure we lock any related online GC tasks to prevent race conditions around the tag creation. See: + digest, err := types.NewDigest(dgst) + if err != nil { + return err + } + artifact := &types.Artifact{ + ImageID: image.ID, + Version: digest.String(), + } - return l.tx.WithTx( - ctx, func(ctx context.Context) error { - // Prevent long running transactions by setting an upper limit of manifestTagGCLockTimeout. If the GC is holding - // the lock of a related review record, the processing there should be fast enough to avoid this. Regardless, we - // should not let transactions open (and clients waiting) for too long. If this sensible timeout is exceeded, abort - // the tag creation and let the client retry. This will bubble up and lead to a 503 Service Unavailable response. - ctx, cancel := context.WithTimeout(ctx, manifestTagGCLockTimeout) - defer cancel() + if err := l.artifactDao.CreateOrUpdate(ctx, artifact); err != nil { + return err + } - if _, err := l.gcService.ManifestFindAndLockBefore( - ctx, dbRepo.ID, dbManifest.ID, - time.Now().Add(manifestTagGCReviewWindow), - ); err != nil && !errors.Is(err, sql.ErrNoRows) { - return err - } + tag := &types.Tag{ + Name: tagName, + ImageName: imageName, + RegistryID: registryID, + ManifestID: manifestID, + } - image := &types.Image{ - Name: imageName, - RegistryID: dbRepo.ID, - Enabled: true, - } + return l.tagDao.CreateOrUpdate(ctx, tag) +} - if err := l.imageDao.CreateOrUpdate(ctx, image); err != nil { - return err - } +// Retrieves the spacePath and packageType. +func (l *manifestService) getSpacePathAndPackageType( + ctx context.Context, + dbRepo *types.Registry, +) (string, event.PackageType, error) { + spacePath, err := l.spacePathStore.FindPrimaryBySpaceID(ctx, dbRepo.ParentID) + if err != nil { + log.Ctx(ctx).Err(err).Msg("Failed to find spacePath") + return "", event.PackageType(0), err + } - digest, err := types.NewDigest(dgst) - if err != nil { - return err - } - artifact := &types.Artifact{ - ImageID: image.ID, - Version: digest.String(), - } + packageType, err := event.GetPackageTypeFromString(string(dbRepo.PackageType)) + if err != nil { + log.Ctx(ctx).Err(err).Msg("Failed to find packageType") + return "", event.PackageType(0), err + } - if err := l.artifactDao.CreateOrUpdate(ctx, artifact); err != nil { - return err - } + return spacePath.Value, packageType, nil +} - tag := &types.Tag{ - Name: tagName, - ImageName: imageName, - RegistryID: dbRepo.ID, - ManifestID: dbManifest.ID, - } - - if err := l.tagDao.CreateOrUpdate(ctx, tag); err != nil { - return err - } - - return nil - }, - ) +// Reports event asynchronously. +func (l *manifestService) reportEventAsync( + ctx context.Context, + regID, + imageName, + tagName string, + packageType event.PackageType, + spacePath string, +) { + go l.reporter.ReportEvent(ctx, &event.ArtifactDetails{ + RegistryID: regID, + ImagePath: imageName + ":" + tagName, + PackageType: packageType, + }, spacePath) } func (l *manifestService) DBPut( @@ -327,7 +410,7 @@ func (l *manifestService) dbPutManifestV2( } dbManifest, err := l.manifestDao.FindManifestByDigest(ctx, dbRepo.ID, info.Image, dgst) - if err != nil && !errors.Is(err, store2.ErrResourceNotFound) { + if err != nil && !errors.Is(err, gitnessstore.ErrResourceNotFound) { return err } @@ -433,7 +516,7 @@ func (l *manifestService) DBFindRepositoryBlob( image := info.Image b, err := l.blobRepo.FindByDigestAndRepoID(ctx, desc.Digest, repoID, image) if err != nil { - if errors.Is(err, store2.ErrResourceNotFound) { + if errors.Is(err, gitnessstore.ErrResourceNotFound) { return nil, fmt.Errorf("blob not found in database") } return nil, err @@ -453,11 +536,11 @@ func (l *manifestService) handleSubject( return err } dbSubject, err := l.manifestDao.FindManifestByDigest(ctx, dbRepo.ID, info.Image, subjectDigest) - if err != nil && !errors.Is(err, store2.ErrResourceNotFound) { + if err != nil && !errors.Is(err, gitnessstore.ErrResourceNotFound) { return err } - if errors.Is(err, store2.ErrResourceNotFound) { + if errors.Is(err, gitnessstore.ErrResourceNotFound) { // in case something happened to the referenced manifest after validation // return distribution.ManifestBlobUnknownError{Digest: subject.Digest} log.Ctx(ctx).Warn().Msgf("subject manifest not found in database") @@ -516,7 +599,7 @@ func (l *manifestService) dbPutManifestList( } ml, err := l.manifestDao.FindManifestByDigest(ctx, r.ID, info.Image, dgst) - if err != nil && !errors.Is(err, store2.ErrResourceNotFound) { + if err != nil && !errors.Is(err, gitnessstore.ErrResourceNotFound) { return err } @@ -625,7 +708,7 @@ func (l *manifestService) dbPutImageIndex( } mi, err := l.manifestDao.FindManifestByDigest(ctx, r.ID, info.Image, dgst) - if err != nil && !errors.Is(err, store2.ErrResourceNotFound) { + if err != nil && !errors.Is(err, gitnessstore.ErrResourceNotFound) { return err } @@ -758,7 +841,7 @@ func (l *manifestService) dbFindManifestListManifest( imageName, dgst, ) if err != nil { - if errors.Is(err, store2.ErrResourceNotFound) { + if errors.Is(err, gitnessstore.ErrResourceNotFound) { return nil, fmt.Errorf( "manifest %s not found for %s/%s", digest.String(), repository.Name, imageName, @@ -849,7 +932,7 @@ func (l *manifestService) DeleteManifest( } m, err := l.manifestDao.FindManifestByDigest(ctx, registry.ID, imageName, newDigest) if err != nil { - if errors.Is(err, store2.ErrResourceNotFound) { + if errors.Is(err, gitnessstore.ErrResourceNotFound) { return util.ErrManifestNotFound } return err diff --git a/registry/app/pkg/docker/wire.go b/registry/app/pkg/docker/wire.go index b3e883c65..96ebdd8a8 100644 --- a/registry/app/pkg/docker/wire.go +++ b/registry/app/pkg/docker/wire.go @@ -16,8 +16,9 @@ package docker import ( "github.com/harness/gitness/app/auth/authz" - corestore "github.com/harness/gitness/app/store" + gitnessstore "github.com/harness/gitness/app/store" storagedriver "github.com/harness/gitness/registry/app/driver" + "github.com/harness/gitness/registry/app/event" "github.com/harness/gitness/registry/app/pkg" "github.com/harness/gitness/registry/app/storage" "github.com/harness/gitness/registry/app/store" @@ -49,17 +50,17 @@ func ManifestServiceProvider( manifestDao store.ManifestRepository, blobRepo store.BlobRepository, mtRepository store.MediaTypesRepository, manifestRefDao store.ManifestReferenceRepository, tagDao store.TagRepository, imageDao store.ImageRepository, artifactDao store.ArtifactRepository, layerDao store.LayerRepository, - gcService gc.Service, tx dbtx.Transactor, + gcService gc.Service, tx dbtx.Transactor, reporter event.Reporter, spacePathStore gitnessstore.SpacePathStore, ) ManifestService { return NewManifestService( registryDao, manifestDao, blobRepo, mtRepository, tagDao, imageDao, - artifactDao, layerDao, manifestRefDao, tx, gcService, + artifactDao, layerDao, manifestRefDao, tx, gcService, reporter, spacePathStore, ) } func RemoteRegistryProvider( local *LocalRegistry, app *App, upstreamProxyConfigRepo store.UpstreamProxyConfigRepository, - spacePathStore corestore.SpacePathStore, secretService secret.Service, + spacePathStore gitnessstore.SpacePathStore, secretService secret.Service, ) *RemoteRegistry { return NewRemoteRegistry(local, app, upstreamProxyConfigRepo, spacePathStore, secretService).(*RemoteRegistry) } @@ -68,7 +69,7 @@ func ControllerProvider( local *LocalRegistry, remote *RemoteRegistry, controller *pkg.CoreController, - spaceStore corestore.SpaceStore, + spaceStore gitnessstore.SpaceStore, authorizer authz.Authorizer, ) *Controller { return NewController(local, remote, controller, spaceStore, authorizer) @@ -78,8 +79,12 @@ func StorageServiceProvider(cfg *types.Config, driver storagedriver.StorageDrive return GetStorageService(cfg, driver) } +func ProvideReporter() event.Reporter { + return &event.Noop{} +} + var ControllerSet = wire.NewSet(ControllerProvider) var RegistrySet = wire.NewSet(LocalRegistryProvider, ManifestServiceProvider, RemoteRegistryProvider) var StorageServiceSet = wire.NewSet(StorageServiceProvider) var AppSet = wire.NewSet(NewApp) -var WireSet = wire.NewSet(ControllerSet, RegistrySet, StorageServiceSet, AppSet, gc.WireSet) +var WireSet = wire.NewSet(ControllerSet, RegistrySet, StorageServiceSet, AppSet) diff --git a/registry/app/store/database.go b/registry/app/store/database.go index d1d5e1c35..b26885a5c 100644 --- a/registry/app/store/database.go +++ b/registry/app/store/database.go @@ -178,15 +178,15 @@ type TagRepository interface { GetAllArtifactsByParentID( ctx context.Context, parentID int64, - packageTypes *[]string, sortByField string, + registryIDs *[]string, sortByField string, sortByOrder string, limit int, offset int, search string, - labels []string, + latestVersion bool, ) (*[]types.ArtifactMetadata, error) CountAllArtifactsByParentID( ctx context.Context, parentID int64, - packageTypes *[]string, search string, - labels []string, + registryIDs *[]string, search string, + latestVersion bool, ) (int64, error) GetAllArtifactsByRepo( @@ -460,4 +460,5 @@ type GCManifestTaskRepository interface { Postpone(ctx context.Context, b *types.GCManifestTask, d time.Duration) error IsDangling(ctx context.Context, b *types.GCManifestTask) (bool, error) Delete(ctx context.Context, b *types.GCManifestTask) error + DeleteManifest(ctx context.Context, registryID, id int64) (*digest.Digest, error) } diff --git a/registry/app/store/database/tag.go b/registry/app/store/database/tag.go index f8936f44f..6d8a7f436 100644 --- a/registry/app/store/database/tag.go +++ b/registry/app/store/database/tag.go @@ -77,6 +77,7 @@ type artifactMetadataDB struct { LatestVersion string `db:"latest_version"` CreatedAt int64 `db:"created_at"` ModifiedAt int64 `db:"modified_at"` + Version string `db:"version"` } type tagMetadataDB struct { @@ -337,54 +338,54 @@ func sqlPartialMatch(value string) string { func (t tagDao) GetAllArtifactsByParentID( ctx context.Context, parentID int64, - packageTypes *[]string, + registryIDs *[]string, sortByField string, sortByOrder string, limit int, offset int, search string, - labels []string, + latestVersion bool, ) (*[]types.ArtifactMetadata, error) { q := databaseg.Builder.Select( - "r.registry_name as repo_name, t.tag_image_name as name,"+ - " r.registry_package_type as package_type, t.tag_name as latest_version,"+ - " t.tag_updated_at as modified_at, ar.image_labels as labels, t2.download_count as download_count ", + `r.registry_name as repo_name, + t.tag_image_name as name, + r.registry_package_type as package_type, + t.tag_name as version, + t.tag_updated_at as modified_at, + ar.image_labels as labels, + COALESCE(t2.download_count,0) as download_count `, ). From("tags t"). - Join( - "(SELECT t.tag_id as id, ROW_NUMBER() OVER "+ - " (PARTITION BY t.tag_registry_id, t.tag_image_name ORDER BY t.tag_updated_at DESC) AS rank "+ - " FROM tags t JOIN registries r ON t.tag_registry_id = r.registry_id "+ - " WHERE r.registry_parent_id = ? ) AS a ON t.tag_id = a.id", parentID, - ). Join("registries r ON t.tag_registry_id = r.registry_id"). + Where("r.registry_parent_id = ?", parentID). Join( "images ar ON ar.image_registry_id = t.tag_registry_id AND"+ " ar.image_name = t.tag_image_name", ). LeftJoin( - "(SELECT i.image_name, t1.download_count FROM"+ - " ( SELECT a.artifact_image_id, COUNT(d.download_stat_id) as download_count"+ - " FROM artifacts a "+ - " LEFT JOIN download_stats d ON d.download_stat_artifact_id = a.artifact_id GROUP BY"+ - " a.artifact_image_id ) as t1 "+ - " JOIN images ON i.image_id = t1.artifact_image_id "+ - " JOIN registries r ON r.registry_id = i.image_registry_id "+ - " WHERE r.registry_parent_id = ? GROUP BY i.image_name) as t2"+ - " ON t.tag_image_name = t2.image_name", parentID, - ). - Where("a.rank = 1") + `( SELECT i.image_name, SUM(COALESCE(t1.download_count, 0)) as download_count FROM + ( SELECT a.artifact_image_id, COUNT(d.download_stat_id) as download_count + FROM artifacts a JOIN download_stats d ON d.download_stat_artifact_id = a.artifact_id + GROUP BY a.artifact_image_id ) as t1 + JOIN images i ON i.image_id = t1.artifact_image_id + JOIN registries r ON r.registry_id = i.image_registry_id + WHERE r.registry_parent_id = ? GROUP BY i.image_name) as t2 + ON t.tag_image_name = t2.image_name`, parentID, + ) - if len(*packageTypes) > 0 { - q = q.Where(sq.Eq{"r.registry_package_type": packageTypes}) + if latestVersion { + q = q.Join( + `(SELECT t.tag_id as id, ROW_NUMBER() OVER (PARTITION BY t.tag_registry_id, t.tag_image_name + ORDER BY t.tag_updated_at DESC) AS rank FROM tags t + JOIN registries r ON t.tag_registry_id = r.registry_id + WHERE r.registry_parent_id = ? ) AS a + ON t.tag_id = a.id`, parentID, // nolint:goconst + ). + Where("a.rank = 1") } - if len(labels) > 0 { - sort.Strings(labels) - labelsVal := util.GetEmptySQLString(util.ArrToString(labels)) - - labelsVal.String = labelSeparatorStart + labelsVal.String + labelSeparatorEnd - q = q.Where("'^_' || ar.image_labels || '^_' LIKE ?", labelsVal) + if len(*registryIDs) > 0 { + q = q.Where(sq.Eq{"r.registry_name": registryIDs}) } if search != "" { @@ -409,39 +410,35 @@ func (t tagDao) GetAllArtifactsByParentID( func (t tagDao) CountAllArtifactsByParentID( ctx context.Context, parentID int64, - packageTypes *[]string, search string, labels []string, + registryIDs *[]string, search string, latestVersion bool, ) (int64, error) { // nolint:goconst q := databaseg.Builder.Select("COUNT(*)"). From("tags t"). - Join( - "(SELECT t.tag_id as id, ROW_NUMBER() OVER "+ - " (PARTITION BY t.tag_registry_id, t.tag_image_name ORDER BY t.tag_updated_at DESC) AS rank FROM tags t "+ - " JOIN registries r ON t.tag_registry_id = r.registry_id "+ - " WHERE r.registry_parent_id = ?) AS a ON t.tag_id = a.id", parentID, - ). - Join("registries r ON t.tag_registry_id = r.registry_id"). + Join("registries r ON t.tag_registry_id = r.registry_id"). // nolint:goconst + Where("r.registry_parent_id = ?", parentID). Join( "images ar ON ar.image_registry_id = t.tag_registry_id" + " AND ar.image_name = t.tag_image_name", - ). - Where("a.rank = 1 ") + ) - if len(*packageTypes) > 0 { - q = q.Where(sq.Eq{"r.registry_package_type": packageTypes}) + if latestVersion { + q = q.Join( + `(SELECT t.tag_id as id, ROW_NUMBER() OVER (PARTITION BY t.tag_registry_id, t.tag_image_name + ORDER BY t.tag_updated_at DESC) AS rank FROM tags t + JOIN registries r ON t.tag_registry_id = r.registry_id + WHERE r.registry_parent_id = ? ) AS a + ON t.tag_id = a.id`, parentID, // nolint:goconst + ).Where("a.rank = 1") + } + if len(*registryIDs) > 0 { + q = q.Where(sq.Eq{"r.registry_name": registryIDs}) } if search != "" { q = q.Where("tag_image_name LIKE ?", sqlPartialMatch(search)) } - if len(labels) > 0 { - sort.Strings(labels) - labelsVal := util.GetEmptySQLString(util.ArrToString(labels)) - labelsVal.String = labelSeparatorStart + labelsVal.String + labelSeparatorEnd - q = q.Where("'^_' || ar.image_labels || '^_' LIKE ?", labelsVal) - } - sql, args, err := q.ToSql() if err != nil { return -1, errors.Wrap(err, "Failed to convert query to sql") @@ -461,9 +458,9 @@ func (t tagDao) GetTagDetail( name string, ) (*types.TagDetail, error) { q := databaseg.Builder.Select( - "tag_id as id, tag_name as name ,"+ - " tag_image_name as image_name, tag_created_at as created_at, "+ - " tag_updated_at as updated_at, manifest_total_size as size", + `tag_id as id, tag_name as name, + tag_image_name as image_name, tag_created_at as created_at, + tag_updated_at as updated_at, manifest_total_size as size`, ). From("tags"). Join("manifests ON manifest_id = tag_manifest_id"). @@ -494,13 +491,13 @@ func (t tagDao) GetLatestTagMetadata( imageName string, ) (*types.ArtifactMetadata, error) { q := databaseg.Builder.Select( - "r.registry_name as repo_name,"+ - " r.registry_package_type as package_type, t.tag_image_name as name, "+ - "t.tag_name as latest_version, t.tag_created_at as created_at,"+ - " t.tag_updated_at as modified_at, ar.image_labels as labels", + `r.registry_name as repo_name, + r.registry_package_type as package_type, t.tag_image_name as name, + t.tag_name as latest_version, t.tag_created_at as created_at, + t.tag_updated_at as modified_at, ar.image_labels as labels`, ). From("tags t"). - Join("registries r ON t.tag_registry_id = r.registry_id"). + Join("registries r ON t.tag_registry_id = r.registry_id"). // nolint:goconst Join( "images ar ON ar.image_registry_id = t.tag_registry_id "+ "AND ar.image_name = t.tag_image_name", @@ -618,18 +615,18 @@ func (t tagDao) GetAllArtifactsByRepo( labels []string, ) (*[]types.ArtifactMetadata, error) { q := databaseg.Builder.Select( - "r.registry_name as repo_name, t.tag_image_name as name,"+ - " r.registry_package_type as package_type, t.tag_name as latest_version,"+ - " t.tag_updated_at as modified_at, ar.image_labels as labels, "+ - " COALESCE(t2.download_count, 0) as download_count ", + `r.registry_name as repo_name, t.tag_image_name as name, + r.registry_package_type as package_type, t.tag_name as latest_version, + t.tag_updated_at as modified_at, ar.image_labels as labels, + COALESCE(t2.download_count, 0) as download_count `, ). From("tags t"). Join( - "(SELECT t.tag_id as id, ROW_NUMBER() OVER (PARTITION BY t.tag_registry_id, t.tag_image_name"+ - " ORDER BY t.tag_updated_at DESC) AS rank FROM tags t "+ - " JOIN registries r ON t.tag_registry_id = r.registry_id "+ - " WHERE r.registry_parent_id = ? AND r.registry_name = ? ) AS a"+ - " ON t.tag_id = a.id", parentID, repoKey, + `(SELECT t.tag_id as id, ROW_NUMBER() OVER (PARTITION BY t.tag_registry_id, t.tag_image_name + ORDER BY t.tag_updated_at DESC) AS rank FROM tags t + JOIN registries r ON t.tag_registry_id = r.registry_id + WHERE r.registry_parent_id = ? AND r.registry_name = ? ) AS a + ON t.tag_id = a.id`, parentID, repoKey, // nolint:goconst ). Join("registries r ON t.tag_registry_id = r.registry_id"). Join( @@ -637,15 +634,15 @@ func (t tagDao) GetAllArtifactsByRepo( " AND ar.image_name = t.tag_image_name", ). LeftJoin( - "( SELECT i.image_name, SUM(COALESCE(t1.download_count, 0)) as download_count FROM"+ - " ( SELECT a.artifact_image_id, COUNT(d.download_stat_id) as download_count"+ - " FROM artifacts a "+ - " JOIN download_stats d ON d.download_stat_artifact_id = a.artifact_id GROUP BY"+ - " a.artifact_image_id ) as t1 "+ - " JOIN images i ON i.image_id = t1.artifact_image_id "+ - " JOIN registries r ON r.registry_id = i.image_registry_id "+ - " WHERE r.registry_parent_id = ? AND r.registry_name = ? GROUP BY i.image_name) as t2"+ - " ON t.tag_image_name = t2.image_name", parentID, repoKey, + `( SELECT i.image_name, SUM(COALESCE(t1.download_count, 0)) as download_count FROM + ( SELECT a.artifact_image_id, COUNT(d.download_stat_id) as download_count + FROM artifacts a + JOIN download_stats d ON d.download_stat_artifact_id = a.artifact_id GROUP BY + a.artifact_image_id ) as t1 + JOIN images i ON i.image_id = t1.artifact_image_id + JOIN registries r ON r.registry_id = i.image_registry_id + WHERE r.registry_parent_id = ? AND r.registry_name = ? GROUP BY i.image_name) as t2 + ON t.tag_image_name = t2.image_name`, parentID, repoKey, ). Where("a.rank = 1 ") @@ -676,6 +673,7 @@ func (t tagDao) GetAllArtifactsByRepo( return t.mapToArtifactMetadataList(ctx, dst) } +// nolint:goconst func (t tagDao) CountAllArtifactsByRepo( ctx context.Context, parentID int64, repoKey string, search string, labels []string, @@ -683,10 +681,10 @@ func (t tagDao) CountAllArtifactsByRepo( q := databaseg.Builder.Select("COUNT(*)"). From("tags t"). Join( - "(SELECT t.tag_id as id, ROW_NUMBER() OVER (PARTITION BY t.tag_registry_id, t.tag_image_name"+ - " ORDER BY t.tag_updated_at DESC) AS rank FROM tags t "+ - " JOIN registries r ON t.tag_registry_id = r.registry_id "+ - " WHERE r.registry_parent_id = ? AND r.registry_name = ? ) AS a ON t.tag_id = a.id", parentID, repoKey, + `(SELECT t.tag_id as id, ROW_NUMBER() OVER (PARTITION BY t.tag_registry_id, t.tag_image_name + ORDER BY t.tag_updated_at DESC) AS rank FROM tags t + JOIN registries r ON t.tag_registry_id = r.registry_id + WHERE r.registry_parent_id = ? AND r.registry_name = ? ) AS a ON t.tag_id = a.id`, parentID, repoKey, ). Join("registries r ON t.tag_registry_id = r.registry_id"). Join( @@ -726,10 +724,10 @@ func (t tagDao) GetAllTagsByRepoAndImage( search string, ) (*[]types.TagMetadata, error) { q := databaseg.Builder.Select( - "t.tag_name as name, m.manifest_total_size as size,"+ - " r.registry_package_type as package_type, t.tag_updated_at as modified_at, "+ - " m.manifest_schema_version, m.manifest_non_conformant, m.manifest_payload, "+ - " mt.mt_media_type ", + `t.tag_name as name, m.manifest_total_size as size, + r.registry_package_type as package_type, t.tag_updated_at as modified_at, + m.manifest_schema_version, m.manifest_non_conformant, m.manifest_payload, + mt.mt_media_type `, ). From("tags t"). Join("registries r ON t.tag_registry_id = r.registry_id"). @@ -903,6 +901,7 @@ func (t tagDao) mapToArtifactMetadata( Labels: util.StringToArr(dst.Labels.String), CreatedAt: time.UnixMilli(dst.CreatedAt), ModifiedAt: time.UnixMilli(dst.ModifiedAt), + Version: dst.Version, }, nil } diff --git a/registry/gc/garbagecollector.go b/registry/gc/garbagecollector.go index edd30c45f..fe79cab2d 100644 --- a/registry/gc/garbagecollector.go +++ b/registry/gc/garbagecollector.go @@ -36,7 +36,7 @@ func New() Service { func (s *Noop) Start( _ context.Context, _ *sqlx.DB, _ corestore.SpaceStore, _ store.BlobRepository, _ storagedriver.StorageDeleter, - _ *types.Config, _ store.MediaTypesRepository, _ store.ManifestRepository, + _ *types.Config, ) { // NOOP } diff --git a/registry/gc/interface.go b/registry/gc/interface.go index 9c0091139..f683c2c49 100644 --- a/registry/gc/interface.go +++ b/registry/gc/interface.go @@ -31,7 +31,7 @@ type Service interface { Start( ctx context.Context, sqlDB *sqlx.DB, spaceStore corestore.SpaceStore, blobRepo store.BlobRepository, storageDeleter storagedriver.StorageDeleter, - config *types.Config, mtRepository store.MediaTypesRepository, manifestRepository store.ManifestRepository, + config *types.Config, ) BlobFindAndLockBefore(ctx context.Context, blobID int64, date time.Time) (*registrytypes.GCBlobTask, error) BlobReschedule(ctx context.Context, b *registrytypes.GCBlobTask, d time.Duration) error diff --git a/registry/types/gc.go b/registry/types/gc.go index 40dab1b9c..91ec696da 100644 --- a/registry/types/gc.go +++ b/registry/types/gc.go @@ -14,15 +14,11 @@ package types -import ( - "time" -) - type GCBlobTask struct { BlobID int64 - ReviewAfter time.Time + ReviewAfter int64 ReviewCount int - CreatedAt time.Time + CreatedAt int64 Event string } @@ -30,14 +26,8 @@ type GCBlobTask struct { type GCManifestTask struct { RegistryID int64 ManifestID int64 - ReviewAfter time.Time + ReviewAfter int64 ReviewCount int - CreatedAt time.Time + CreatedAt int64 Event string } - -// GCReviewAfterDefault represents a row in the gc_review_after_defaults table. -type GCReviewAfterDefault struct { - Event string - Value time.Duration -} diff --git a/registry/types/tag.go b/registry/types/tag.go index b1080d368..bd62ea284 100644 --- a/registry/types/tag.go +++ b/registry/types/tag.go @@ -42,6 +42,7 @@ type ArtifactMetadata struct { LatestVersion string CreatedAt time.Time ModifiedAt time.Time + Version string } type TagMetadata struct { diff --git a/web/package.json b/web/package.json index a63e89573..1f38be7df 100644 --- a/web/package.json +++ b/web/package.json @@ -51,7 +51,7 @@ "@codemirror/view": "^6.9.6", "@harnessio/design-system": "^2.1.1", "@harnessio/icons": "^2.1.7", - "@harnessio/react-har-service-client": "^0.0.15", + "@harnessio/react-har-service-client": "^0.0.21", "@harnessio/uicore": "^4.1.2", "@tanstack/react-query": "4.20.4", "@types/dompurify": "^3.0.2", diff --git a/web/src/ar/MFEAppTypes.ts b/web/src/ar/MFEAppTypes.ts index a4304cddc..f47b579bd 100644 --- a/web/src/ar/MFEAppTypes.ts +++ b/web/src/ar/MFEAppTypes.ts @@ -15,9 +15,9 @@ */ import type React from 'react' -import type { Button } from '@harnessio/uicore' import type { PermissionIdentifier, ResourceType } from '@ar/common/permissionTypes' +import type RbacButton from '@ar/__mocks__/components/RbacButton' import type RbacMenuItem from '@ar/__mocks__/components/RbacMenuItem' import type NGBreadcrumbs from '@ar/__mocks__/components/NGBreadcrumbs' import type DependencyView from '@ar/__mocks__/components/DependencyView' @@ -43,6 +43,16 @@ export interface Scope { space?: string } +export interface PipelineExecutionPathProps { + executionIdentifier: string + pipelineIdentifier: string + module: 'ci' | 'cd' +} + +export interface ServiceDetailsPathProps { + serviceId: string +} + export interface PermissionsRequest { resource: { resourceType: ResourceType; resourceIdentifier?: string } permissions: PermissionIdentifier[] @@ -63,7 +73,7 @@ export interface ParentContextObj { } export interface Components { - RbacButton: typeof Button + RbacButton: typeof RbacButton NGBreadcrumbs: typeof NGBreadcrumbs RbacMenuItem: typeof RbacMenuItem } @@ -96,6 +106,8 @@ export interface CustomUtils { getCustomHeaders: () => Record getApiBaseUrl: (url: string) => string getRouteDefinitions?: (routeParams: Record) => ARRouteDefinitionsReturn + getRouteToPipelineExecutionView?: (params: Scope & PipelineExecutionPathProps) => string + getRouteToServiceDetailsView?: (params: Scope & ServiceDetailsPathProps) => string } export interface MFEAppProps { diff --git a/web/src/ar/__mocks__/components/RbacButton.tsx b/web/src/ar/__mocks__/components/RbacButton.tsx new file mode 100644 index 000000000..8ce8affe5 --- /dev/null +++ b/web/src/ar/__mocks__/components/RbacButton.tsx @@ -0,0 +1,29 @@ +/* + * Copyright 2024 Harness, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react' +import { Button, ButtonProps } from '@harnessio/uicore' + +import type { PermissionsRequest } from '@ar/MFEAppTypes' +import type { PermissionIdentifier } from '@ar/common/permissionTypes' + +export interface RbacButtonProps extends ButtonProps { + permission?: Omit & { permission: PermissionIdentifier } +} + +export default function RbacButton(props: RbacButtonProps) { + return + ) +} + interface RepositoryLocationBadgeProps { value: RepositoryConfigType } @@ -158,26 +202,58 @@ const ToggleAccordionCell = (props: ToggleAccordionCellProps): JSX.Element => { interface LinkCellProps { label: string + subLabel?: string prefix?: React.ReactElement postfix?: React.ReactElement linkTo: string } const LinkCell = (props: LinkCellProps): JSX.Element => { - const { prefix, postfix, label, linkTo } = props + const { prefix, postfix, label, linkTo, subLabel } = props return ( - + {prefix} - - - {label} - - + + + + {label} + + + {subLabel && {subLabel}} + {postfix} ) } +interface DeploymentsCellProps { + prodCount?: number + nonProdCount?: number +} + +export const DeploymentsCell = ({ prodCount, nonProdCount }: DeploymentsCellProps) => { + return ( + + + {defaultTo(prodCount, 0)} + + + + {defaultTo(nonProdCount, 0)} + + + + ) +} + +export const PullCommandCell = ({ value }: CommonCellProps) => { + const { getString } = useStrings() + if (!value) return <>{getString('na')} + return +} + export default { UrlCell, SizeCell, @@ -185,6 +261,9 @@ export default { LinkCell, TextCell, CopyUrlCell, + CopyTextCell, + DeploymentsCell, + PullCommandCell, LastModifiedCell, ToggleAccordionCell, RepositoryLocationBadgeCell diff --git a/web/src/ar/components/Tag/Tag.module.scss b/web/src/ar/components/Tag/Tag.module.scss index 529cfa67f..58ca6d313 100644 --- a/web/src/ar/components/Tag/Tag.module.scss +++ b/web/src/ar/components/Tag/Tag.module.scss @@ -31,3 +31,19 @@ color: var(--grey-0) !important; } } + +.tag { + font-size: 10px; + font-weight: 700; + line-height: 15px; +} + +.prodTag { + background-color: var(--blue-50) !important; + color: var(--blue-900) !important; +} + +.nonProdTag { + background-color: var(--teal-50) !important; + color: var(--team-900) !important; +} diff --git a/web/src/ar/components/Tag/Tag.module.scss.d.ts b/web/src/ar/components/Tag/Tag.module.scss.d.ts index 0152d483b..9ff4efd57 100644 --- a/web/src/ar/components/Tag/Tag.module.scss.d.ts +++ b/web/src/ar/components/Tag/Tag.module.scss.d.ts @@ -17,4 +17,7 @@ /* eslint-disable */ // This is an auto-generated file export declare const artifactTag: string +export declare const nonProdTag: string +export declare const prodTag: string +export declare const tag: string export declare const versionTag: string diff --git a/web/src/ar/components/Tag/Tags.tsx b/web/src/ar/components/Tag/Tags.tsx new file mode 100644 index 000000000..574941595 --- /dev/null +++ b/web/src/ar/components/Tag/Tags.tsx @@ -0,0 +1,41 @@ +/* + * Copyright 2024 Harness, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react' +import classNames from 'classnames' +import { Tag } from '@blueprintjs/core' + +import { useStrings } from '@ar/frameworks/strings' + +import css from './Tag.module.scss' + +export function ProdTag() { + const { getString } = useStrings() + return ( + + {getString('prod')} + + ) +} + +export function NonProdTag() { + const { getString } = useStrings() + return ( + + {getString('nonProd')} + + ) +} diff --git a/web/src/ar/pages/artifact-details/components/ArtifactActions/DeleteRepository.tsx b/web/src/ar/pages/artifact-details/components/ArtifactActions/DeleteRepository.tsx index 0e3163cf3..938dba603 100644 --- a/web/src/ar/pages/artifact-details/components/ArtifactActions/DeleteRepository.tsx +++ b/web/src/ar/pages/artifact-details/components/ArtifactActions/DeleteRepository.tsx @@ -50,10 +50,10 @@ export default function DeleteRepositoryMenuItem({ repoKey }: ArtifactActionProp onClick={handleDeleteService} permission={{ resource: { - resourceType: ResourceType.SERVICE, + resourceType: ResourceType.ARTIFACT_REGISTRY, resourceIdentifier: repoKey }, - permission: PermissionIdentifier.DELETE_SERVICE + permission: PermissionIdentifier.DELETE_ARTIFACT_REGISTRY }} /> ) diff --git a/web/src/ar/pages/artifact-details/components/ArtifactActions/EditRepository.tsx b/web/src/ar/pages/artifact-details/components/ArtifactActions/EditRepository.tsx index 7fc6561d8..ac431e03b 100644 --- a/web/src/ar/pages/artifact-details/components/ArtifactActions/EditRepository.tsx +++ b/web/src/ar/pages/artifact-details/components/ArtifactActions/EditRepository.tsx @@ -47,10 +47,10 @@ export default function EditRepositoryMenuItem({ repoKey }: ArtifactActionProps) onClick={handleOpenRepository} permission={{ resource: { - resourceType: ResourceType.SERVICE, + resourceType: ResourceType.ARTIFACT_REGISTRY, resourceIdentifier: repoKey }, - permission: PermissionIdentifier.EDIT_SERVICE + permission: PermissionIdentifier.EDIT_ARTIFACT_REGISTRY }} /> diff --git a/web/src/ar/pages/artifact-details/components/ArtifactActions/SetupClient.tsx b/web/src/ar/pages/artifact-details/components/ArtifactActions/SetupClient.tsx index 93eba6717..ec9ec53c2 100644 --- a/web/src/ar/pages/artifact-details/components/ArtifactActions/SetupClient.tsx +++ b/web/src/ar/pages/artifact-details/components/ArtifactActions/SetupClient.tsx @@ -43,10 +43,10 @@ export default function SetupClientMenuItem({ data, repoKey }: ArtifactActionPro onClick={showSetupClientModal} permission={{ resource: { - resourceType: ResourceType.SERVICE, + resourceType: ResourceType.ARTIFACT_REGISTRY, resourceIdentifier: defaultTo(repoKey, '') }, - permission: PermissionIdentifier.DELETE_SERVICE + permission: PermissionIdentifier.VIEW_ARTIFACT_REGISTRY }} /> diff --git a/web/src/ar/pages/artifact-details/components/ArtifactDetailsHeader/ArtifactDetailsHeaderContent.tsx b/web/src/ar/pages/artifact-details/components/ArtifactDetailsHeader/ArtifactDetailsHeaderContent.tsx index 4e26cdd1c..88f92aabb 100644 --- a/web/src/ar/pages/artifact-details/components/ArtifactDetailsHeader/ArtifactDetailsHeaderContent.tsx +++ b/web/src/ar/pages/artifact-details/components/ArtifactDetailsHeader/ArtifactDetailsHeaderContent.tsx @@ -30,6 +30,7 @@ import WeeklyDownloads from '@ar/components/PageTitle/WeeklyDownloads' import CreatedAndModifiedAt from '@ar/components/PageTitle/CreatedAndModifiedAt' import ArtifactTags from '@ar/components/PageTitle/ArtifactTags' import NameAndDescription from '@ar/components/PageTitle/NameAndDescription' +import { PermissionIdentifier, ResourceType } from '@ar/common/permissionTypes' import { useSetupClientModal } from '@ar/pages/repository-details/hooks/useSetupClientModal/useSetupClientModal' import RepositoryIcon from '@ar/frameworks/RepositoryStep/RepositoryIcon' @@ -109,6 +110,13 @@ function ArtifactDetailsHeaderContent(props: ArtifactDetailsHeaderContentProps): onChange={handleUpdateArtifactLabels} labels={defaultTo(labels, [])} placeholder={getString('artifactDetails.artifactLabelInputPlaceholder')} + permission={{ + permission: PermissionIdentifier.EDIT_ARTIFACT_REGISTRY, + resource: { + resourceType: ResourceType.ARTIFACT_REGISTRY, + resourceIdentifier: repositoryIdentifier + } + }} /> diff --git a/web/src/ar/pages/artifact-list/ArtifactListPage.module.scss b/web/src/ar/pages/artifact-list/ArtifactListPage.module.scss index ec84c9020..222a7027d 100644 --- a/web/src/ar/pages/artifact-list/ArtifactListPage.module.scss +++ b/web/src/ar/pages/artifact-list/ArtifactListPage.module.scss @@ -35,3 +35,12 @@ width: 100%; } } + +.filterTabContainer { + display: flex; + align-items: center; + + & button { + --button-height: 32px !important; + } +} diff --git a/web/src/ar/pages/artifact-list/ArtifactListPage.module.scss.d.ts b/web/src/ar/pages/artifact-list/ArtifactListPage.module.scss.d.ts index a2b31e874..562af042a 100644 --- a/web/src/ar/pages/artifact-list/ArtifactListPage.module.scss.d.ts +++ b/web/src/ar/pages/artifact-list/ArtifactListPage.module.scss.d.ts @@ -16,6 +16,7 @@ /* eslint-disable */ // This is an auto-generated file +export declare const filterTabContainer: string export declare const pageBody: string export declare const subHeader: string export declare const subHeaderItems: string diff --git a/web/src/ar/pages/artifact-list/ArtifactListPage.tsx b/web/src/ar/pages/artifact-list/ArtifactListPage.tsx index c9e46b768..123fc03a0 100644 --- a/web/src/ar/pages/artifact-list/ArtifactListPage.tsx +++ b/web/src/ar/pages/artifact-list/ArtifactListPage.tsx @@ -14,47 +14,38 @@ * limitations under the License. */ -import React, { useCallback, useMemo, useRef } from 'react' +import React, { useMemo } from 'react' import classNames from 'classnames' -import { flushSync } from 'react-dom' -import { defaultTo } from 'lodash-es' import { Expander } from '@blueprintjs/core' +import { HarnessDocTooltip, Page, Button, ButtonVariation } from '@harnessio/uicore' import { - ExpandingSearchInput, - HarnessDocTooltip, - Page, - type ExpandingSearchInputHandle, - Button, - ButtonVariation -} from '@harnessio/uicore' -import { useGetAllArtifactsQuery } from '@harnessio/react-har-service-client' + GetAllHarnessArtifactsQueryQueryParams, + useGetAllHarnessArtifactsQuery +} from '@harnessio/react-har-service-client' import { useStrings } from '@ar/frameworks/strings' import { DEFAULT_PAGE_INDEX, PreferenceScope } from '@ar/constants' +import { ButtonTab, ButtonTabs } from '@ar/components/ButtonTabs/ButtonTabs' import { useGetSpaceRef, useParentComponents, useParentHooks } from '@ar/hooks' import PackageTypeSelector from '@ar/components/PackageTypeSelector/PackageTypeSelector' -import RepositorySelector from './components/RepositorySelector/RepositorySelector' -import ArtifactListTable from './components/ArtifactListTable/ArtifactListTable' + +import { ArtifactListVersionFilter } from './constants' import LabelsSelector from './components/LabelsSelector/LabelsSelector' +import ArtifactListTable from './components/ArtifactListTable/ArtifactListTable' +import RepositorySelector from './components/RepositorySelector/RepositorySelector' +import ArtifactSearchInput from './components/ArtifactSearchInput/ArtifactSearchInput' import { useArtifactListQueryParamOptions, type ArtifactListPageQueryParams } from './utils' + import css from './ArtifactListPage.module.scss' -interface ArtifactListPageProps { - withHeader?: boolean - parentRepoKey?: string - pageBodyClassName?: string -} - -function ArtifactListPage({ withHeader = true, parentRepoKey, pageBodyClassName }: ArtifactListPageProps): JSX.Element { +function ArtifactListPage(): JSX.Element { const { getString } = useStrings() const { NGBreadcrumbs } = useParentComponents() const { useQueryParams, useUpdateQueryParams, usePreferenceStore } = useParentHooks() - const searchRef = useRef({} as ExpandingSearchInputHandle) const { updateQueryParams } = useUpdateQueryParams>() const queryParams = useQueryParams(useArtifactListQueryParamOptions()) - const { searchTerm, isDeployedArtifacts, packageTypes, repositoryKey, page, size, labels } = queryParams - const shouldRenderRepositorySelectFilter = !parentRepoKey - const shouldRenderPackageTypeSelectFilter = !parentRepoKey + const { searchTerm, isDeployedArtifacts, repositoryKey, page, size, latestVersion, packageTypes, labels } = + queryParams const spaceRef = useGetSpaceRef('') const { preference: sortingPreference, setPreference: setSortingPreference } = usePreferenceStore( @@ -73,108 +64,108 @@ function ArtifactListPage({ withHeader = true, parentRepoKey, pageBodyClassName refetch, isLoading: loading, error - } = useGetAllArtifactsQuery({ + } = useGetAllHarnessArtifactsQuery({ space_ref: spaceRef, queryParams: { page, size, - label: labels, - package_type: packageTypes, search_term: searchTerm, sort_field: sortField, sort_order: sortOrder, - reg_identifier: defaultTo(parentRepoKey, repositoryKey) - }, + reg_identifier: repositoryKey ? [repositoryKey] : undefined, + latest_version: latestVersion, + deployed_artifact: isDeployedArtifacts, + package_type: packageTypes, + label: labels + } as GetAllHarnessArtifactsQueryQueryParams, stringifyQueryParamsOptions: { arrayFormat: 'repeat' } }) const handleClearAllFilters = (): void => { - flushSync(searchRef.current.clear) updateQueryParams({ page: 0, searchTerm: '', - packageTypes: [], - isDeployedArtifacts: false + isDeployedArtifacts: false, + latestVersion: false }) } - const handleClickLabel = useCallback( - (val: string) => { - if (labels.includes(val)) return - updateQueryParams({ - labels: [...labels, val], - page: DEFAULT_PAGE_INDEX - }) - }, - [labels] - ) - - const hasFilter = !!searchTerm || packageTypes.length || isDeployedArtifacts + const hasFilter = !!searchTerm || isDeployedArtifacts || latestVersion const responseData = data?.content?.data return ( <> - {withHeader && ( - -

{getString('artifactList.pageHeading')}

- - - } - breadcrumbs={} - /> - )} + +

{getString('artifactList.pageHeading')}

+ + + } + breadcrumbs={} + />
- {shouldRenderRepositorySelectFilter && ( - { - updateQueryParams({ repositoryKey: val, page: DEFAULT_PAGE_INDEX }) - }} - /> - )} - {shouldRenderPackageTypeSelectFilter && ( - { - updateQueryParams({ packageTypes: val, page: DEFAULT_PAGE_INDEX }) - }} - /> - )} + { + updateQueryParams({ searchTerm: text || undefined, page: DEFAULT_PAGE_INDEX }) + }} + placeholder={getString('search')} + /> + { + updateQueryParams({ repositoryKey: val, page: DEFAULT_PAGE_INDEX }) + }} + /> + { + updateQueryParams({ packageTypes: val, page: DEFAULT_PAGE_INDEX }) + }} + /> { updateQueryParams({ labels: val, page: DEFAULT_PAGE_INDEX }) }} /> - {/* TODO: removed till BE support this filter */} - {/* { - updateQueryParams({ isDeployedArtifacts: val, page: DEFAULT_PAGE_INDEX }) - }} - /> */} - { - updateQueryParams({ searchTerm: text || undefined, page: DEFAULT_PAGE_INDEX }) - }} - defaultValue={searchTerm} - ref={searchRef} - /> + { + updateQueryParams({ + latestVersion: newTab === ArtifactListVersionFilter.LATEST_VERSION, + page: DEFAULT_PAGE_INDEX + }) + }}> + } + title={getString('artifactList.table.latestVersions')} + /> + } + title={getString('artifactList.table.allVersions')} + /> +
refetch()} @@ -199,7 +190,6 @@ function ArtifactListPage({ withHeader = true, parentRepoKey, pageBodyClassName setSortingPreference(JSON.stringify(sortArray)) updateQueryParams({ sort: sortArray }) }} - onClickLabel={handleClickLabel} sortBy={sort} /> )} diff --git a/web/src/ar/pages/artifact-list/RegistryArtifactListPage.tsx b/web/src/ar/pages/artifact-list/RegistryArtifactListPage.tsx new file mode 100644 index 000000000..8cd399136 --- /dev/null +++ b/web/src/ar/pages/artifact-list/RegistryArtifactListPage.tsx @@ -0,0 +1,161 @@ +/* + * Copyright 2024 Harness, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { useCallback, useMemo, useRef } from 'react' +import classNames from 'classnames' +import { flushSync } from 'react-dom' +import { Expander } from '@blueprintjs/core' +import { ExpandingSearchInput, Page, type ExpandingSearchInputHandle, Button, ButtonVariation } from '@harnessio/uicore' +import { useGetAllArtifactsByRegistryQuery } from '@harnessio/react-har-service-client' + +import { useStrings } from '@ar/frameworks/strings' +import { DEFAULT_PAGE_INDEX, PreferenceScope } from '@ar/constants' +import { useGetSpaceRef, useParentHooks } from '@ar/hooks' +import LabelsSelector from './components/LabelsSelector/LabelsSelector' +import { + useRegistryArtifactListQueryParamOptions, + type RegistryArtifactListPageQueryParams +} from './components/RegistryArtifactListTable/utils' +import RegistryArtifactListTable from './components/RegistryArtifactListTable/RegistryArtifactListTable' + +import css from './ArtifactListPage.module.scss' + +interface RegistryArtifactListPageProps { + pageBodyClassName?: string +} + +function RegistryArtifactListPage({ pageBodyClassName }: RegistryArtifactListPageProps): JSX.Element { + const { getString } = useStrings() + const { useQueryParams, useUpdateQueryParams, usePreferenceStore } = useParentHooks() + const searchRef = useRef({} as ExpandingSearchInputHandle) + const { updateQueryParams } = useUpdateQueryParams>() + const queryParams = useQueryParams(useRegistryArtifactListQueryParamOptions()) + const { searchTerm, isDeployedArtifacts, packageTypes, page, size, labels } = queryParams + const registryRef = useGetSpaceRef() + + const { preference: sortingPreference, setPreference: setSortingPreference } = usePreferenceStore( + PreferenceScope.USER, + 'ArtifactRepositorySortingPreference' + ) + const sort = useMemo( + () => (sortingPreference ? JSON.parse(sortingPreference) : queryParams.sort), + [queryParams.sort, sortingPreference] + ) + + const [sortField, sortOrder] = sort || [] + + const { + data, + refetch, + isLoading: loading, + error + } = useGetAllArtifactsByRegistryQuery({ + registry_ref: registryRef, + queryParams: { + page, + size, + search_term: searchTerm, + sort_field: sortField, + sort_order: sortOrder + }, + stringifyQueryParamsOptions: { + arrayFormat: 'repeat' + } + }) + + const handleClearAllFilters = (): void => { + flushSync(searchRef.current.clear) + updateQueryParams({ + page: 0, + searchTerm: '', + packageTypes: [], + isDeployedArtifacts: false + }) + } + + const handleClickLabel = useCallback( + (val: string) => { + if (labels.includes(val)) return + updateQueryParams({ + labels: [...labels, val], + page: DEFAULT_PAGE_INDEX + }) + }, + [labels] + ) + + const hasFilter = !!searchTerm || packageTypes.length || isDeployedArtifacts + const responseData = data?.content?.data + + return ( + <> + +
+ { + updateQueryParams({ labels: val, page: DEFAULT_PAGE_INDEX }) + }} + /> + + { + updateQueryParams({ searchTerm: text || undefined, page: DEFAULT_PAGE_INDEX }) + }} + defaultValue={searchTerm} + ref={searchRef} + /> +
+
+ refetch()} + noData={{ + when: () => !responseData?.artifacts?.length, + // image: getEmptyStateIllustration(hasFilter, module), + icon: 'container', + messageTitle: hasFilter ? getString('noResultsFound') : getString('artifactList.table.noArtifactsTitle'), + button: hasFilter ? ( + ) } + +export const ScanStatusCell: CellType = ({ row }) => { + const { original } = row + const router = useRoutes() + const { version = '', name, registryIdentifier } = original + const { getString } = useStrings() + const linkTo = router.toARVersionDetailsTab({ + repositoryIdentifier: registryIdentifier, + artifactIdentifier: name, + versionIdentifier: version, + versionTab: VersionDetailsTab.OVERVIEW + }) + return ( + + + {getString('artifactList.table.actions.VulnerabilityStatus.scanStatus')} + + + ) +} + +export const LatestArtifactCell: CellType = ({ row }) => { + const { original } = row + return +} diff --git a/web/src/ar/pages/artifact-list/components/ArtifactSearchInput/ArtifactSearchInput.tsx b/web/src/ar/pages/artifact-list/components/ArtifactSearchInput/ArtifactSearchInput.tsx new file mode 100644 index 000000000..0fc5a01a6 --- /dev/null +++ b/web/src/ar/pages/artifact-list/components/ArtifactSearchInput/ArtifactSearchInput.tsx @@ -0,0 +1,29 @@ +/* + * Copyright 2024 Harness, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react' +import AISearchInput from '@ar/components/AISearchInput/AISearchInput' +import { ArtifactListAIOptions } from './constants' + +interface ArtifactSearchInputProps { + searchTerm: string + onChange: (val: string) => void + placeholder?: string +} + +export default function ArtifactSearchInput(props: ArtifactSearchInputProps) { + return +} diff --git a/web/src/ar/pages/artifact-list/components/ArtifactSearchInput/constants.ts b/web/src/ar/pages/artifact-list/components/ArtifactSearchInput/constants.ts new file mode 100644 index 000000000..128794221 --- /dev/null +++ b/web/src/ar/pages/artifact-list/components/ArtifactSearchInput/constants.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2024 Harness, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { AIOption } from '@ar/components/AISearchInput/types' + +export const ArtifactListAIOptions: AIOption[] = [ + { + label: 'Need help? Here are some prompts example:', + value: '-1', + disabled: true + }, + { + label: 'Show me artifacts not downloaded in last 30 days', + value: 'Show me artifacts not downloaded in last 30 days' + }, + { + label: 'Find out artifacts in prod but not deployed in lower environments', + value: 'Find out artifacts in prod but not deployed in lower environments' + }, + { + label: 'List artifacts in prod but with critical vulnerabilities', + value: 'List artifacts in prod but with critical vulnerabilities' + } +] diff --git a/web/src/ar/pages/artifact-list/components/RegistryArtifactListTable/RegistryArtifactListTable.module.scss b/web/src/ar/pages/artifact-list/components/RegistryArtifactListTable/RegistryArtifactListTable.module.scss new file mode 100644 index 000000000..f89d1b894 --- /dev/null +++ b/web/src/ar/pages/artifact-list/components/RegistryArtifactListTable/RegistryArtifactListTable.module.scss @@ -0,0 +1,47 @@ +/* + * Copyright 2024 Harness, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.table { + --typography-size: 12px; + --typography-weight: 400; + --line-height: 20px; + padding: var(--spacing-large); + + display: grid; + grid-template-rows: max-content 1fr max-content; + row-gap: var(--spacing-small); + min-height: inherit; + + [role='cell'], + [role='columnheader'] { + width: auto !important; + padding-right: var(--spacing-small); + } + + div[class*='TableV2--cells'], + div[class*='TableV2--header'] { + display: grid !important; + grid-template-columns: 1fr 15rem 20rem 15rem; + } +} + +.tableRow { + position: relative; +} + +.nameCellContainer { + align-items: center; +} diff --git a/web/src/ar/pages/artifact-list/components/RegistryArtifactListTable/RegistryArtifactListTable.module.scss.d.ts b/web/src/ar/pages/artifact-list/components/RegistryArtifactListTable/RegistryArtifactListTable.module.scss.d.ts new file mode 100644 index 000000000..f4faa7448 --- /dev/null +++ b/web/src/ar/pages/artifact-list/components/RegistryArtifactListTable/RegistryArtifactListTable.module.scss.d.ts @@ -0,0 +1,21 @@ +/* + * Copyright 2023 Harness, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* eslint-disable */ +// This is an auto-generated file +export declare const nameCellContainer: string +export declare const table: string +export declare const tableRow: string diff --git a/web/src/ar/pages/artifact-list/components/RegistryArtifactListTable/RegistryArtifactListTable.tsx b/web/src/ar/pages/artifact-list/components/RegistryArtifactListTable/RegistryArtifactListTable.tsx new file mode 100644 index 000000000..e09afc0c0 --- /dev/null +++ b/web/src/ar/pages/artifact-list/components/RegistryArtifactListTable/RegistryArtifactListTable.tsx @@ -0,0 +1,112 @@ +/* + * Copyright 2024 Harness, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react' +import classNames from 'classnames' +import type { Column } from 'react-table' +import { PaginationProps, TableV2 } from '@harnessio/uicore' +import type { ArtifactMetadata, ListArtifact } from '@harnessio/react-har-service-client' + +import { useStrings } from '@ar/frameworks/strings' +import { useParentHooks } from '@ar/hooks' +import { + RegistryArtifactDownloadsCell, + RegistryArtifactLatestUpdatedCell, + RegistryArtifactNameCell, + RepositoryNameCell +} from './RegistryArtifactListTableCell' +import css from './RegistryArtifactListTable.module.scss' + +export interface RegistryArtifactListColumnActions { + refetchList?: () => void +} +export interface RegistryArtifactListTableProps extends RegistryArtifactListColumnActions { + data: ListArtifact + gotoPage: (pageNumber: number) => void + onPageSizeChange?: PaginationProps['onPageSizeChange'] + setSortBy: (sortBy: string[]) => void + sortBy: string[] + minimal?: boolean + onClickLabel: (val: string) => void +} + +export default function RegistryArtifactListTable(props: RegistryArtifactListTableProps): JSX.Element { + const { data, gotoPage, onPageSizeChange, sortBy, setSortBy, onClickLabel } = props + const { useDefaultPaginationProps } = useParentHooks() + const { getString } = useStrings() + + const { artifacts = [], itemCount = 0, pageCount = 0, pageIndex, pageSize = 0 } = data || {} + const paginationProps = useDefaultPaginationProps({ + itemCount, + pageSize, + pageCount, + pageIndex, + gotoPage, + onPageSizeChange + }) + const [currentSort, currentOrder] = sortBy + + const columns: Column[] = React.useMemo(() => { + const getServerSortProps = (id: string) => { + return { + enableServerSort: true, + isServerSorted: currentSort === id, + isServerSortedDesc: currentOrder === 'DESC', + getSortedColumn: ({ sort }: any) => { + setSortBy([sort, currentOrder === 'DESC' ? 'ASC' : 'DESC']) + } + } + } + return [ + { + Header: getString('artifactList.table.columns.name'), + accessor: 'name', + Cell: RegistryArtifactNameCell, + serverSortProps: getServerSortProps('name'), + onClickLabel + }, + { + Header: getString('artifactList.table.columns.repository'), + accessor: 'registryIdentifier', + Cell: RepositoryNameCell, + serverSortProps: getServerSortProps('registryIdentifier') + }, + { + Header: getString('artifactList.table.columns.downloads'), + accessor: 'downloadsCount', + Cell: RegistryArtifactDownloadsCell, + serverSortProps: getServerSortProps('downloadsCount') + }, + { + Header: getString('artifactList.table.columns.latestVersion'), + accessor: 'latestVersion', + Cell: RegistryArtifactLatestUpdatedCell, + serverSortProps: getServerSortProps('latestVersion') + } + ].filter(Boolean) as unknown as Column[] + }, [currentOrder, currentSort, getString, onClickLabel]) + + return ( + + className={classNames(css.table)} + columns={columns} + data={artifacts} + pagination={paginationProps} + sortable + getRowClassName={() => css.tableRow} + /> + ) +} diff --git a/web/src/ar/pages/artifact-list/components/RegistryArtifactListTable/RegistryArtifactListTableCell.tsx b/web/src/ar/pages/artifact-list/components/RegistryArtifactListTable/RegistryArtifactListTableCell.tsx new file mode 100644 index 000000000..64a647efd --- /dev/null +++ b/web/src/ar/pages/artifact-list/components/RegistryArtifactListTable/RegistryArtifactListTableCell.tsx @@ -0,0 +1,136 @@ +/* + * Copyright 2024 Harness, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react' +import { defaultTo } from 'lodash-es' +import { Position } from '@blueprintjs/core' +import type { Cell, CellValue, ColumnInstance, Renderer, Row, TableInstance } from 'react-table' +import { Layout, Text } from '@harnessio/uicore' +import { Color } from '@harnessio/design-system' +import type { RegistryArtifactMetadata } from '@harnessio/react-har-service-client' + +import { useRoutes } from '@ar/hooks' +import Tag from '@ar/components/Tag/Tag' +import { useStrings } from '@ar/frameworks/strings' +import TableCells from '@ar/components/TableCells/TableCells' +import LabelsPopover from '@ar/components/LabelsPopover/LabelsPopover' +import RepositoryIcon from '@ar/frameworks/RepositoryStep/RepositoryIcon' +import type { RepositoryPackageType } from '@ar/common/types' +import { RepositoryDetailsTab } from '@ar/pages/repository-details/constants' + +type CellTypeWithActions, V = any> = TableInstance & { + column: ColumnInstance + row: Row + cell: Cell + value: CellValue +} + +type CellType = Renderer> + +type RegistryArtifactNameCellActionProps = { + onClickLabel: (val: string) => void +} + +export const RegistryArtifactNameCell: Renderer<{ + row: Row + column: ColumnInstance & RegistryArtifactNameCellActionProps +}> = ({ row, column }) => { + const { original } = row + const { onClickLabel } = column + const routes = useRoutes() + const value = original.name + return ( + } + linkTo={routes.toARArtifactDetails({ + repositoryIdentifier: original.registryIdentifier, + artifactIdentifier: value + })} + label={value} + postfix={ + onClickLabel(e.currentTarget.ariaValueText as string) + }} + /> + } + /> + ) +} + +export const RegistryArtifactTagsCell: CellType = ({ value }) => { + const { getString } = useStrings() + if (!Array.isArray(value) || !value.length) { + return ( + + {getString('na')} + + ) + } + return ( + + {Array.isArray(value) && + value.map(each => ( + + {each} + + ))} + + ) +} + +export const RepositoryNameCell: CellType = ({ value }) => { + const routes = useRoutes() + return ( + + ) +} + +export const RegistryArtifactDownloadsCell: CellType = ({ value }) => { + return +} + +export const RegistryArtifactLatestUpdatedCell: CellType = ({ row }) => { + const { getString } = useStrings() + const { original } = row + const { latestVersion, lastModified } = original || {} + if (!latestVersion) { + return ( + + {getString('na')} + + ) + } + return ( + + + {latestVersion} + + + + ) +} diff --git a/web/src/ar/pages/artifact-list/components/RegistryArtifactListTable/utils.ts b/web/src/ar/pages/artifact-list/components/RegistryArtifactListTable/utils.ts new file mode 100644 index 000000000..ecad4cea5 --- /dev/null +++ b/web/src/ar/pages/artifact-list/components/RegistryArtifactListTable/utils.ts @@ -0,0 +1,52 @@ +/* + * Copyright 2024 Harness, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useMemo } from 'react' + +import { useParentHooks } from '@ar/hooks' +import type { RepositoryPackageType } from '@ar/common/types' +import { DEFAULT_PAGE_INDEX, DEFAULT_PAGE_SIZE, DEFAULT_PIPELINE_LIST_TABLE_SORT } from '@ar/constants' +import type { UseQueryParamsOptions } from '@ar/__mocks__/hooks' + +export type RegistryArtifactListPageQueryParams = { + page: number + size: number + sort: string[] + searchTerm?: string + isDeployedArtifacts: boolean + packageTypes: RepositoryPackageType[] + repositoryKey?: string + labels: string[] +} + +export const useRegistryArtifactListQueryParamOptions = + (): UseQueryParamsOptions => { + const { useQueryParamsOptions } = useParentHooks() + const _options = useQueryParamsOptions( + { + page: DEFAULT_PAGE_INDEX, + size: DEFAULT_PAGE_SIZE, + sort: DEFAULT_PIPELINE_LIST_TABLE_SORT, + isDeployedArtifacts: false, + packageTypes: [], + labels: [] + }, + { ignoreEmptyString: false } + ) + const options = useMemo(() => ({ ..._options, strictNullHandling: true }), [_options]) + + return options + } diff --git a/web/src/ar/pages/artifact-list/constants.ts b/web/src/ar/pages/artifact-list/constants.ts new file mode 100644 index 000000000..3833efc91 --- /dev/null +++ b/web/src/ar/pages/artifact-list/constants.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2024 Harness, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export enum ArtifactListVersionFilter { + LATEST_VERSION = 'latest_version', + ALL_VERSION = 'all_version' +} diff --git a/web/src/ar/pages/artifact-list/strings/strings.en.yaml b/web/src/ar/pages/artifact-list/strings/strings.en.yaml index 441980683..117dded8a 100644 --- a/web/src/ar/pages/artifact-list/strings/strings.en.yaml +++ b/web/src/ar/pages/artifact-list/strings/strings.en.yaml @@ -3,13 +3,26 @@ pageHeading: Artifacts deployedArtifacts: Deployed Artifacts table: noArtifactsTitle: There are no artifact available. - allRepositories: All Registries + allRepositories: Registries + latestVersions: Latest Versions + allVersions: All Versions columns: name: Name + artifactName: Artifact Name repository: Registry tags: Labels - downloads: Downloads (In the last week) + downloads: Downloads latestVersion: Latest Version + pullCommand: Pull Command + environments: Environments + sto: Security Vulnerabilities + lastUpdated: Uploaded At actions: editRepository: Edit Registry deleteRepository: Delete Registry + VulnerabilityStatus: + scanned: Scanned + nonScanned: Not Scanned + scanStatus: Scan Status + partiallyScanned: '{{ scanned }}/{{ total }} Scanned' + digestMenuItemText: '{{archName}} (digest: {{digest}})' diff --git a/web/src/ar/pages/artifact-list/utils.ts b/web/src/ar/pages/artifact-list/utils.ts index 3b3126946..f6cdd0afa 100644 --- a/web/src/ar/pages/artifact-list/utils.ts +++ b/web/src/ar/pages/artifact-list/utils.ts @@ -17,29 +17,22 @@ import { useMemo } from 'react' import { useParentHooks } from '@ar/hooks' -import type { RepositoryPackageType } from '@ar/common/types' import { DEFAULT_PAGE_INDEX, DEFAULT_PAGE_SIZE, DEFAULT_PIPELINE_LIST_TABLE_SORT } from '@ar/constants' +import type { RepositoryPackageType } from '@ar/common/types' import type { UseQueryParamsOptions } from '@ar/__mocks__/hooks' -type GetArtifactListQueryParams = { - accountIdentifier: string - orgIdentifier: string - projectIdentifier: string +export type ArtifactListPageQueryParams = { page: number size: number sort: string[] - searchTerm?: string - isDeployedArtifacts: boolean packageTypes: RepositoryPackageType[] - repositoryKey?: string labels: string[] + latestVersion: boolean + isDeployedArtifacts: boolean + searchTerm?: string + repositoryKey?: string } -export type ArtifactListPageQueryParams = Omit< - GetArtifactListQueryParams, - 'accountIdentifier' | 'orgIdentifier' | 'projectIdentifier' -> - export const useArtifactListQueryParamOptions = (): UseQueryParamsOptions => { const { useQueryParamsOptions } = useParentHooks() const _options = useQueryParamsOptions( @@ -48,10 +41,11 @@ export const useArtifactListQueryParamOptions = (): UseQueryParamsOptions ({ ..._options, strictNullHandling: true }), [_options]) diff --git a/web/src/ar/pages/digest-list/components/DigestListTable/DigestListTable.module.scss b/web/src/ar/pages/digest-list/components/DigestListTable/DigestListTable.module.scss index 5941a036d..3e3f2d284 100644 --- a/web/src/ar/pages/digest-list/components/DigestListTable/DigestListTable.module.scss +++ b/web/src/ar/pages/digest-list/components/DigestListTable/DigestListTable.module.scss @@ -34,7 +34,7 @@ div[class*='TableV2--cells'], div[class*='TableV2--header'] { display: grid !important; - grid-template-columns: 1fr 10rem 10rem 10rem 20rem 50px !important; + grid-template-columns: 1fr 10rem 10rem 10rem 20rem 10rem 50px !important; } div[class*='TableV2--header'] { diff --git a/web/src/ar/pages/digest-list/components/DigestListTable/DigestListTable.tsx b/web/src/ar/pages/digest-list/components/DigestListTable/DigestListTable.tsx index de5536c97..a3f83c125 100644 --- a/web/src/ar/pages/digest-list/components/DigestListTable/DigestListTable.tsx +++ b/web/src/ar/pages/digest-list/components/DigestListTable/DigestListTable.tsx @@ -26,6 +26,7 @@ import { DigestNameCell, DownloadsCell, OsArchCell, + ScanStatusCell, SizeCell, UploadedByCell } from './DigestTableCells' @@ -69,6 +70,12 @@ export default function DigestListTable(props: DigestListTableProps): JSX.Elemen accessor: 'downloadsCount', Cell: DownloadsCell }, + { + Header: getString('digestList.table.columns.scanStatus'), + accessor: 'scanStatus', + Cell: ScanStatusCell, + version + }, { Header: '', accessor: 'menu', diff --git a/web/src/ar/pages/digest-list/components/DigestListTable/DigestTableCells.tsx b/web/src/ar/pages/digest-list/components/DigestListTable/DigestTableCells.tsx index b52a5c92b..d81cbda32 100644 --- a/web/src/ar/pages/digest-list/components/DigestListTable/DigestTableCells.tsx +++ b/web/src/ar/pages/digest-list/components/DigestListTable/DigestTableCells.tsx @@ -15,13 +15,18 @@ */ import React from 'react' +import { Link } from 'react-router-dom' import type { Cell, CellValue, ColumnInstance, Renderer, Row, TableInstance } from 'react-table' + +import { Text } from '@harnessio/uicore' +import { Color } from '@harnessio/design-system' import type { DockerManifestDetails } from '@harnessio/react-har-service-client' +import { useStrings } from '@ar/frameworks/strings' import { useDecodedParams, useRoutes } from '@ar/hooks' -import type { ArtifactDetailsPathParams } from '@ar/routes/types' -import TableCells from '@ar/components/TableCells/TableCells' import { getShortDigest } from '@ar/pages/digest-list/utils' +import TableCells from '@ar/components/TableCells/TableCells' +import type { ArtifactDetailsPathParams } from '@ar/routes/types' import { VersionDetailsTab } from '@ar/pages/version-details/components/VersionDetailsTabs/constants' type CellTypeWithActions, V = any> = TableInstance & { @@ -72,6 +77,42 @@ export const DownloadsCell: CellType = ({ value }) => { return } +export const ScanStatusCell: Renderer<{ + row: Row + column: ColumnInstance & DigestNameColumnProps +}> = ({ row, column }) => { + const { original } = row + const { version } = column + const router = useRoutes() + const { stoExecutionId, stoPipelineId, digest } = original + const pathParams = useDecodedParams() + const { getString } = useStrings() + if (!stoExecutionId || !stoPipelineId) + return + + const linkTo = router.toARVersionDetailsTab({ + repositoryIdentifier: pathParams.repositoryIdentifier, + artifactIdentifier: pathParams.artifactIdentifier, + versionIdentifier: version, + versionTab: VersionDetailsTab.SECURITY_TESTS, + pipelineIdentifier: stoPipelineId, + executionIdentifier: stoExecutionId + }) + return ( + + + {getString('artifactList.table.actions.VulnerabilityStatus.scanned')} + + + ) +} + export const DigestActionsCell: CellType = () => { return <> } diff --git a/web/src/ar/pages/digest-list/strings/strings.en.yaml b/web/src/ar/pages/digest-list/strings/strings.en.yaml index bcf05d873..f8ddfdde0 100644 --- a/web/src/ar/pages/digest-list/strings/strings.en.yaml +++ b/web/src/ar/pages/digest-list/strings/strings.en.yaml @@ -6,5 +6,6 @@ table: digest: Digest osArch: OS/Arch size: Size - uploadedBy: Uploaded By + uploadedBy: Uploaded At downloads: Downloads/Last Download + scanStatus: Scan Status diff --git a/web/src/ar/pages/repository-details/RepositoryDetails.tsx b/web/src/ar/pages/repository-details/RepositoryDetails.tsx index 0e1a42305..32d626254 100644 --- a/web/src/ar/pages/repository-details/RepositoryDetails.tsx +++ b/web/src/ar/pages/repository-details/RepositoryDetails.tsx @@ -16,31 +16,33 @@ import React, { useCallback, useContext } from 'react' import type { FormikProps } from 'formik' -import { useParams } from 'react-router-dom' import { Expander } from '@blueprintjs/core' import { Button, ButtonVariation, Container, Layout, Tab, Tabs } from '@harnessio/uicore' -import { useParentHooks } from '@ar/hooks' -import { useStrings } from '@ar/frameworks/strings' import type { RepositoryDetailsPathParams } from '@ar/routes/types' -import ArtifactListPage from '@ar/pages/artifact-list/ArtifactListPage' +import { useDecodedParams, useParentComponents, useParentHooks } from '@ar/hooks' +import { PermissionIdentifier, ResourceType } from '@ar/common/permissionTypes' +import { useStrings } from '@ar/frameworks/strings' import type { RepositoryConfigType, RepositoryPackageType } from '@ar/common/types' import RepositoryDetailsHeaderWidget from '@ar/frameworks/RepositoryStep/RepositoryDetailsHeaderWidget' import RepositoryConfigurationFormWidget from '@ar/frameworks/RepositoryStep/RepositoryConfigurationFormWidget' -import { RepositoryProviderContext } from './context/RepositoryProvider' + import type { Repository } from './types' import { RepositoryDetailsTab } from './constants' +import { RepositoryProviderContext } from './context/RepositoryProvider' +import RegistryArtifactListPage from '../artifact-list/RegistryArtifactListPage' import css from './RepositoryDetailsPage.module.scss' export default function RepositoryDetails(): JSX.Element | null { - const pathParams = useParams() const { useUpdateQueryParams, useQueryParams } = useParentHooks() const { updateQueryParams } = useUpdateQueryParams() + const { RbacButton } = useParentComponents() const { getString } = useStrings() + const { repositoryIdentifier } = useDecodedParams() const { tab: selectedTabId = RepositoryDetailsTab.PACKAGES } = useQueryParams<{ tab: RepositoryDetailsTab }>() const stepRef = React.useRef | null>(null) - const { isDirty, data } = useContext(RepositoryProviderContext) + const { isDirty, data, isReadonly, isUpdating } = useContext(RepositoryProviderContext) const { config } = data as Repository const { type } = config @@ -62,12 +64,19 @@ export default function RepositoryDetails(): JSX.Element | null { const renderActionBtns = (): JSX.Element => ( -