zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/cli/client/discover.go (about) 1 //go:build search 2 // +build search 3 4 package client 5 6 import ( 7 "context" 8 "fmt" 9 10 distext "github.com/opencontainers/distribution-spec/specs-go/v1/extensions" 11 12 zerr "zotregistry.dev/zot/errors" 13 "zotregistry.dev/zot/pkg/api/constants" 14 zcommon "zotregistry.dev/zot/pkg/common" 15 ) 16 17 type field struct { 18 Name string `json:"name"` 19 Args []struct { 20 Name string `json:"name"` 21 } `json:"args"` 22 } 23 24 type schemaList struct { 25 Data struct { 26 Schema struct { 27 QueryType struct { 28 Fields []field `json:"fields"` 29 } `json:"queryType"` //nolint:tagliatelle // graphQL schema 30 Types []typeInfo `json:"types"` 31 } `json:"__schema"` //nolint:tagliatelle // graphQL schema 32 } `json:"data"` 33 Errors []zcommon.ErrorGQL `json:"errors"` 34 } 35 36 type typeInfo struct { 37 Name string `json:"name"` 38 Fields []typeField `json:"fields"` 39 } 40 41 type typeField struct { 42 Name string `json:"name"` 43 } 44 45 func containsGQLQueryWithParams(queryList []field, serverGQLTypesList []typeInfo, requiredQueries ...GQLQuery) error { 46 serverGQLTypes := map[string][]typeField{} 47 48 for _, typeInfo := range serverGQLTypesList { 49 serverGQLTypes[typeInfo.Name] = typeInfo.Fields 50 } 51 52 for _, reqQuery := range requiredQueries { 53 foundQuery := false 54 55 for _, query := range queryList { 56 if query.Name == reqQuery.Name && haveSameArgs(query, reqQuery) { 57 foundQuery = true 58 } 59 } 60 61 if !foundQuery { 62 return fmt.Errorf("%w: %s", zerr.ErrGQLQueryNotSupported, reqQuery.Name) 63 } 64 65 // let's check just the name of the returned type 66 returnType := reqQuery.ReturnType.Name 67 68 // we can next define fields of the returned types and check them recursively 69 // for now we will just check the name of the returned type to be known by the server 70 _, ok := serverGQLTypes[returnType] 71 if !ok { 72 return fmt.Errorf("%w: server doesn't support needed type '%s'", zerr.ErrGQLQueryNotSupported, returnType) 73 } 74 } 75 76 return nil 77 } 78 79 func haveSameArgs(query field, reqQuery GQLQuery) bool { 80 if len(query.Args) != len(reqQuery.Args) { 81 return false 82 } 83 84 for i := range query.Args { 85 if query.Args[i].Name != reqQuery.Args[i] { 86 return false 87 } 88 } 89 90 return true 91 } 92 93 func CheckExtEndPointQuery(config SearchConfig, requiredQueries ...GQLQuery) error { 94 username, password := getUsernameAndPassword(config.User) 95 ctx := context.Background() 96 97 discoverEndPoint, err := combineServerAndEndpointURL(config.ServURL, fmt.Sprintf("%s%s", 98 constants.RoutePrefix, constants.ExtOciDiscoverPrefix)) 99 if err != nil { 100 return err 101 } 102 103 discoverResponse := &distext.ExtensionList{} 104 105 _, err = makeGETRequest(ctx, discoverEndPoint, username, password, config.VerifyTLS, 106 config.Debug, &discoverResponse, config.ResultWriter) 107 if err != nil { 108 return err 109 } 110 111 searchEnabled := false 112 113 for _, extension := range discoverResponse.Extensions { 114 if extension.Name == constants.BaseExtension { 115 for _, endpoint := range extension.Endpoints { 116 if endpoint == constants.FullSearchPrefix { 117 searchEnabled = true 118 } 119 } 120 } 121 } 122 123 if !searchEnabled { 124 return fmt.Errorf("%w: search extension gql endpoints not found", zerr.ErrExtensionNotEnabled) 125 } 126 127 searchEndPoint, _ := combineServerAndEndpointURL(config.ServURL, constants.FullSearchPrefix) 128 129 schemaQuery := ` 130 { 131 __schema() { 132 queryType { 133 fields { 134 name 135 args { 136 name 137 } 138 type { 139 name 140 kind 141 } 142 } 143 __typename 144 } 145 types { 146 name 147 fields { 148 name 149 } 150 } 151 } 152 }` 153 154 queryResponse := &schemaList{} 155 156 err = makeGraphQLRequest(ctx, searchEndPoint, schemaQuery, username, password, config.VerifyTLS, 157 config.Debug, queryResponse, config.ResultWriter) 158 if err != nil { 159 return fmt.Errorf("gql query failed: %w", err) 160 } 161 162 if err = checkResultGraphQLQuery(ctx, err, queryResponse.Errors); err != nil { 163 return fmt.Errorf("gql query failed: %w", err) 164 } 165 166 return containsGQLQueryWithParams(queryResponse.Data.Schema.QueryType.Fields, 167 queryResponse.Data.Schema.Types, requiredQueries...) 168 }