2022-10-23 01:44:20 -05:00
|
|
|
//go:build search
|
|
|
|
// +build search
|
|
|
|
|
|
|
|
package cli
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
distext "github.com/opencontainers/distribution-spec/specs-go/v1/extensions"
|
|
|
|
|
2023-08-30 12:12:24 -05:00
|
|
|
zerr "zotregistry.io/zot/errors"
|
2022-10-23 01:44:20 -05:00
|
|
|
"zotregistry.io/zot/pkg/api/constants"
|
2023-08-30 12:12:24 -05:00
|
|
|
zcommon "zotregistry.io/zot/pkg/common"
|
2022-10-23 01:44:20 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
type field struct {
|
|
|
|
Name string `json:"name"`
|
2023-08-30 12:12:24 -05:00
|
|
|
Args []struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
} `json:"args"`
|
2022-10-23 01:44:20 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
type schemaList struct {
|
|
|
|
Data struct {
|
|
|
|
Schema struct {
|
|
|
|
QueryType struct {
|
|
|
|
Fields []field `json:"fields"`
|
|
|
|
} `json:"queryType"` //nolint:tagliatelle // graphQL schema
|
2023-08-30 12:12:24 -05:00
|
|
|
Types []typeInfo `json:"types"`
|
2022-10-23 01:44:20 -05:00
|
|
|
} `json:"__schema"` //nolint:tagliatelle // graphQL schema
|
|
|
|
} `json:"data"`
|
2023-08-30 12:12:24 -05:00
|
|
|
Errors []zcommon.ErrorGQL `json:"errors"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type typeInfo struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
Fields []typeField `json:"fields"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type typeField struct {
|
|
|
|
Name string `json:"name"`
|
2022-10-23 01:44:20 -05:00
|
|
|
}
|
|
|
|
|
2023-08-30 12:12:24 -05:00
|
|
|
func containsGQLQueryWithParams(queryList []field, serverGQLTypesList []typeInfo, requiredQueries ...GQLQuery) error {
|
|
|
|
serverGQLTypes := map[string][]typeField{}
|
|
|
|
|
|
|
|
for _, typeInfo := range serverGQLTypesList {
|
|
|
|
serverGQLTypes[typeInfo.Name] = typeInfo.Fields
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, reqQuery := range requiredQueries {
|
|
|
|
foundQuery := false
|
|
|
|
|
|
|
|
for _, query := range queryList {
|
|
|
|
if query.Name == reqQuery.Name && haveSameArgs(query, reqQuery) {
|
|
|
|
foundQuery = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !foundQuery {
|
|
|
|
return fmt.Errorf("%w: %s", zerr.ErrGQLQueryNotSupported, reqQuery.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
// let's check just the name of the returned type
|
|
|
|
returnType := reqQuery.ReturnType.Name
|
|
|
|
|
|
|
|
// we can next define fields of the returned types and check them recursively
|
|
|
|
// for now we will just check the name of the returned type to be known by the server
|
|
|
|
_, ok := serverGQLTypes[returnType]
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("%w: server doesn't support needed type '%s'", zerr.ErrGQLQueryNotSupported, returnType)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func haveSameArgs(query field, reqQuery GQLQuery) bool {
|
|
|
|
if len(query.Args) != len(reqQuery.Args) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := range query.Args {
|
|
|
|
if query.Args[i].Name != reqQuery.Args[i] {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func CheckExtEndPointQuery(config searchConfig, requiredQueries ...GQLQuery) error {
|
2023-09-08 07:12:47 -05:00
|
|
|
username, password := getUsernameAndPassword(config.user)
|
2023-08-30 12:12:24 -05:00
|
|
|
ctx := context.Background()
|
|
|
|
|
2023-09-08 07:12:47 -05:00
|
|
|
discoverEndPoint, err := combineServerAndEndpointURL(config.servURL, fmt.Sprintf("%s%s",
|
2023-08-30 12:12:24 -05:00
|
|
|
constants.RoutePrefix, constants.ExtOciDiscoverPrefix))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
discoverResponse := &distext.ExtensionList{}
|
|
|
|
|
2023-09-08 07:12:47 -05:00
|
|
|
_, err = makeGETRequest(ctx, discoverEndPoint, username, password, config.verifyTLS,
|
|
|
|
config.debug, &discoverResponse, config.resultWriter)
|
2023-08-30 12:12:24 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
searchEnabled := false
|
|
|
|
|
|
|
|
for _, extension := range discoverResponse.Extensions {
|
|
|
|
if extension.Name == constants.BaseExtension {
|
|
|
|
for _, endpoint := range extension.Endpoints {
|
|
|
|
if endpoint == constants.FullSearchPrefix {
|
|
|
|
searchEnabled = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !searchEnabled {
|
|
|
|
return fmt.Errorf("%w: search extension gql endpoints not found", zerr.ErrExtensionNotEnabled)
|
|
|
|
}
|
|
|
|
|
2023-09-08 07:12:47 -05:00
|
|
|
searchEndPoint, _ := combineServerAndEndpointURL(config.servURL, constants.FullSearchPrefix)
|
2023-08-30 12:12:24 -05:00
|
|
|
|
|
|
|
schemaQuery := `
|
|
|
|
{
|
|
|
|
__schema() {
|
|
|
|
queryType {
|
|
|
|
fields {
|
|
|
|
name
|
|
|
|
args {
|
|
|
|
name
|
|
|
|
}
|
|
|
|
type {
|
|
|
|
name
|
|
|
|
kind
|
|
|
|
}
|
|
|
|
}
|
|
|
|
__typename
|
|
|
|
}
|
|
|
|
types {
|
|
|
|
name
|
|
|
|
fields {
|
|
|
|
name
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}`
|
|
|
|
|
|
|
|
queryResponse := &schemaList{}
|
|
|
|
|
2023-09-08 07:12:47 -05:00
|
|
|
err = makeGraphQLRequest(ctx, searchEndPoint, schemaQuery, username, password, config.verifyTLS,
|
|
|
|
config.debug, queryResponse, config.resultWriter)
|
2023-08-30 12:12:24 -05:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("gql query failed: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = checkResultGraphQLQuery(ctx, err, queryResponse.Errors); err != nil {
|
|
|
|
return fmt.Errorf("gql query failed: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return containsGQLQueryWithParams(queryResponse.Data.Schema.QueryType.Fields,
|
|
|
|
queryResponse.Data.Schema.Types, requiredQueries...)
|
|
|
|
}
|