diff --git a/db/migrations/000004_create_config_table.up.sql b/db/migrations/000004_create_config_table.up.sql index 32d64c4b..34c957f7 100644 --- a/db/migrations/000004_create_config_table.up.sql +++ b/db/migrations/000004_create_config_table.up.sql @@ -10,3 +10,4 @@ CREATE TABLE "config" ( PRIMARY KEY(namespace, reference) ); +CREATE INDEX on config (substr(namespace,1,20) text_pattern_ops); diff --git a/registry/v2/registry.go b/registry/v2/registry.go index ec500c03..7c4e13e1 100644 --- a/registry/v2/registry.go +++ b/registry/v2/registry.go @@ -829,3 +829,20 @@ func (r *registry) ApiVersion(ctx echo.Context) error { return ctx.String(http.StatusOK, "OK\n") } + +func (r *registry) GetImageNamespace(ctx echo.Context) error { + searchQuery := ctx.QueryParam("search_query") + if searchQuery == "" { + return ctx.JSON(http.StatusBadRequest, echo.Map{ + "error": "search query must not be empty", + }) + } + result, err := r.store.GetImageNamespace(ctx.Request().Context(), searchQuery) + if err != nil { + return ctx.JSON(http.StatusInternalServerError, echo.Map{ + "error": err.Error(), + "message": "error getting image namespace", + }) + } + return ctx.JSON(http.StatusOK, result) +} diff --git a/registry/v2/types.go b/registry/v2/types.go index 60fd2a02..d106e9ac 100644 --- a/registry/v2/types.go +++ b/registry/v2/types.go @@ -272,4 +272,5 @@ type Registry interface { DeleteTagOrManifest(ctx echo.Context) error //The list of available repositories is made available through the catalog Catalog(ctx echo.Context) error + GetImageNamespace(ctx echo.Context) error } diff --git a/router/route_names.go b/router/route_names.go index 299f7411..e95994b6 100644 --- a/router/route_names.go +++ b/router/route_names.go @@ -48,4 +48,5 @@ const ( // JWT based auth endpoint TokenAuth = "/token" + Search = "/catalog/search" ) diff --git a/router/router.go b/router/router.go index b5e73486..a53fb095 100644 --- a/router/router.go +++ b/router/router.go @@ -53,7 +53,7 @@ func Register( RegisterAuthRoutes(authRouter, authSvc) RegisterBetaRoutes(betaRouter, localCache) InternalRoutes(internal, localCache) - Docker(v2Router, reg) + Extensions(v2Router, reg) } // RegisterNSRoutes is one of the helper functions to Register @@ -109,9 +109,11 @@ func RegisterNSRoutes(nsRouter *echo.Group, reg registry.Registry) { nsRouter.Add(http.MethodDelete, ManifestsReference, reg.DeleteTagOrManifest) } -// Docker is used for Catalog api -func Docker(group *echo.Group, reg registry.Registry) { +// Extensions for teh OCI dist spec +func Extensions(group *echo.Group, reg registry.Registry) { // GET /v2/_catalog group.Add(http.MethodGet, Catalog, reg.Catalog) + group.Add(http.MethodGet, Search, reg.GetImageNamespace) + } diff --git a/store/postgres/container_image.go b/store/postgres/container_image.go index 22ab5e76..d987e7b0 100644 --- a/store/postgres/container_image.go +++ b/store/postgres/container_image.go @@ -336,3 +336,23 @@ func (p *pg) Metadata(ctx echo.Context) error { return ctx.JSON(http.StatusOK, imageManifestList) } + +func (p *pg) GetImageNamespace(ctx context.Context, search string) ([]string, error) { + childCtx, cancel := context.WithTimeout(context.Background(), time.Minute*30) + defer cancel() + rows, err := p.conn.Query(childCtx, queries.GetImageNamespace, "%"+search+"%") + if err != nil { + return nil, fmt.Errorf("ERR_QUERY_GET_IMAGE_NAMESPACE: %w", err) + } + defer rows.Close() + + var result []string + for rows.Next() { + var ns string + if err := rows.Scan(&ns); err != nil { + return nil, fmt.Errorf("ERR_IMAGE_NAMESPACE_SCAN: %w", err) + } + result = append(result, ns) + } + return result, nil +} diff --git a/store/postgres/postgres.go b/store/postgres/postgres.go index 202a1174..b32c5577 100644 --- a/store/postgres/postgres.go +++ b/store/postgres/postgres.go @@ -63,6 +63,7 @@ type RegistryStore interface { GetImageTags(ctx context.Context, namespace string) ([]string, error) GetCatalog(ctx context.Context, namespace string, pageSize int64, offset int64) ([]*types.ConfigV2, error) GetCatalogCount(ctx context.Context) (int64, error) + GetImageNamespace(ctx context.Context, search string) ([]string, error) DeleteLayerV2(ctx context.Context, txn pgx.Tx, digest string) error DeleteBlobV2(ctx context.Context, txn pgx.Tx, digest string) error DeleteManifestOrTag(ctx context.Context, txn pgx.Tx, reference string) error diff --git a/store/postgres/queries/registry.go b/store/postgres/queries/registry.go index 5a0baf31..51bca49b 100644 --- a/store/postgres/queries/registry.go +++ b/store/postgres/queries/registry.go @@ -33,6 +33,7 @@ var ( GetCatalog = `select uuid,namespace,reference,digest from config;` GetCatalogWithPagination = `select uuid,namespace,reference,digest from config limit $1 offset $2;` GetUserCatalogWithPagination = `select uuid,namespace,reference,digest from config where namespace like $1 limit $2 offset $3;` + GetImageNamespace = `select namespace from config where substr(namespace, 1, 50) like $1;` ) // delete queries