Skip to content

Commit

Permalink
fix Incorrectly generated query strings (that rely on + not being url…
Browse files Browse the repository at this point in the history
…encoded)

    In the Kubernetes API client, we should be able to generate queries like
    ?labelSelector=environment+in+%28production%2Cqa%29%2Ctier+in+%28frontend%29
    (from https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#list-and-watch-filtering).
    When the generated URIs URL-encode the plus signs, the API server rejects them.

    With this change, (newConfig :: IO KubernetesClientConfig) now sets
    the default configQueryExtraUnreserved setting to "+". Requests
    generated with this config object will avoid URI encoding "+" all
    querystring params.
  • Loading branch information
jonschoning committed Sep 20, 2021
1 parent edfb474 commit 541fb76
Show file tree
Hide file tree
Showing 11 changed files with 120 additions and 16 deletions.
8 changes: 4 additions & 4 deletions kubernetes-client/kubernetes-client.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ cabal-version: 1.12
-- see: https://github.com/sol/hpack

name: kubernetes-client
version: 0.4.1.0
version: 0.4.2.0
synopsis: Client library for Kubernetes
description: Client library for interacting with a Kubernetes cluster.
.
Expand Down Expand Up @@ -60,7 +60,7 @@ library
, http-client-tls >=0.3
, jose-jwt >=0.8
, jsonpath >=0.1 && <0.3
, kubernetes-client-core ==0.4.1.0
, kubernetes-client-core ==0.4.2.0
, microlens >=0.4
, mtl >=2.2
, oidc-client >=0.4
Expand Down Expand Up @@ -105,7 +105,7 @@ test-suite example
, jose-jwt >=0.8
, jsonpath >=0.1 && <0.3
, kubernetes-client
, kubernetes-client-core ==0.4.1.0
, kubernetes-client-core ==0.4.2.0
, microlens >=0.4
, mtl >=2.2
, oidc-client >=0.4
Expand Down Expand Up @@ -158,7 +158,7 @@ test-suite spec
, jose-jwt >=0.8
, jsonpath >=0.1 && <0.3
, kubernetes-client
, kubernetes-client-core ==0.4.1.0
, kubernetes-client-core ==0.4.2.0
, microlens >=0.4
, mtl >=2.2
, oidc-client >=0.4
Expand Down
4 changes: 2 additions & 2 deletions kubernetes-client/package.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: kubernetes-client
version: 0.4.1.0
version: 0.4.2.0
description: |
Client library for interacting with a Kubernetes cluster.
Expand Down Expand Up @@ -49,7 +49,7 @@ dependencies:
- http-client >=0.5 && <0.8
- http-client-tls >=0.3
- jose-jwt >=0.8
- kubernetes-client-core ==0.4.1.0
- kubernetes-client-core ==0.4.2.0
- microlens >=0.4
- mtl >=2.2
- oidc-client >=0.4
Expand Down
4 changes: 2 additions & 2 deletions kubernetes/.openapi-generator/COMMIT
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
Requested Commit: df877a8cf9b5e2b7a02c6e88dc4767b1e25c0b6f
Actual Commit: df877a8cf9b5e2b7a02c6e88dc4767b1e25c0b6f
Requested Commit: 1247e774530b715fb54f719a3b10000d5dd2137b
Actual Commit: 1247e774530b715fb54f719a3b10000d5dd2137b
2 changes: 1 addition & 1 deletion kubernetes/kubernetes-client-core.cabal
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: kubernetes-client-core
version: 0.4.1.0
version: 0.4.2.0
synopsis: Auto-generated kubernetes-client-core API Client
description: .
Client library for calling the Kubernetes API based on http-client.
Expand Down
20 changes: 20 additions & 0 deletions kubernetes/lib/Kubernetes/OpenAPI/API/CustomObjects.hs
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,11 @@ instance HasOptionalParam ListClusterCustomObject Pretty where
applyOptionalParam req (Pretty xs) =
req `addQuery` toQuery ("pretty", Just xs)

-- | /Optional Param/ "allowWatchBookmarks" - allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored. If the feature gate WatchBookmarks is not enabled in apiserver, this field is ignored.
instance HasOptionalParam ListClusterCustomObject AllowWatchBookmarks where
applyOptionalParam req (AllowWatchBookmarks xs) =
req `addQuery` toQuery ("allowWatchBookmarks", Just xs)

-- | /Optional Param/ "continue" - The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\". This field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.
instance HasOptionalParam ListClusterCustomObject Continue where
applyOptionalParam req (Continue xs) =
Expand All @@ -575,6 +580,11 @@ instance HasOptionalParam ListClusterCustomObject ResourceVersion where
applyOptionalParam req (ResourceVersion xs) =
req `addQuery` toQuery ("resourceVersion", Just xs)

-- | /Optional Param/ "resourceVersionMatch" - resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. Defaults to unset
instance HasOptionalParam ListClusterCustomObject ResourceVersionMatch where
applyOptionalParam req (ResourceVersionMatch xs) =
req `addQuery` toQuery ("resourceVersionMatch", Just xs)

-- | /Optional Param/ "timeoutSeconds" - Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.
instance HasOptionalParam ListClusterCustomObject TimeoutSeconds where
applyOptionalParam req (TimeoutSeconds xs) =
Expand Down Expand Up @@ -616,6 +626,11 @@ instance HasOptionalParam ListNamespacedCustomObject Pretty where
applyOptionalParam req (Pretty xs) =
req `addQuery` toQuery ("pretty", Just xs)

-- | /Optional Param/ "allowWatchBookmarks" - allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored. If the feature gate WatchBookmarks is not enabled in apiserver, this field is ignored.
instance HasOptionalParam ListNamespacedCustomObject AllowWatchBookmarks where
applyOptionalParam req (AllowWatchBookmarks xs) =
req `addQuery` toQuery ("allowWatchBookmarks", Just xs)

-- | /Optional Param/ "continue" - The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\". This field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.
instance HasOptionalParam ListNamespacedCustomObject Continue where
applyOptionalParam req (Continue xs) =
Expand All @@ -641,6 +656,11 @@ instance HasOptionalParam ListNamespacedCustomObject ResourceVersion where
applyOptionalParam req (ResourceVersion xs) =
req `addQuery` toQuery ("resourceVersion", Just xs)

-- | /Optional Param/ "resourceVersionMatch" - resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. Defaults to unset
instance HasOptionalParam ListNamespacedCustomObject ResourceVersionMatch where
applyOptionalParam req (ResourceVersionMatch xs) =
req `addQuery` toQuery ("resourceVersionMatch", Just xs)

-- | /Optional Param/ "timeoutSeconds" - Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.
instance HasOptionalParam ListNamespacedCustomObject TimeoutSeconds where
applyOptionalParam req (TimeoutSeconds xs) =
Expand Down
14 changes: 10 additions & 4 deletions kubernetes/lib/Kubernetes/OpenAPI/Client.hs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import qualified Control.Exception.Safe as E
import qualified Control.Monad.IO.Class as P
import qualified Control.Monad as P
import qualified Data.Aeson.Types as A
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as BC
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString.Lazy.Char8 as BCL
Expand Down Expand Up @@ -179,13 +180,18 @@ _toInitRequest config req0 =
(configValidateAuthMethods config && (not . null . rAuthTypes) req1)
(E.throw $ AuthMethodException $ "AuthMethod not configured: " <> (show . head . rAuthTypes) req1)
let req2 = req1 & _setContentTypeHeader & _setAcceptHeader
reqHeaders = ("User-Agent", WH.toHeader (configUserAgent config)) : paramsHeaders (rParams req2)
reqQuery = NH.renderQuery True (paramsQuery (rParams req2))
pReq = parsedReq { NH.method = (rMethod req2)
params = rParams req2
reqHeaders = ("User-Agent", WH.toHeader (configUserAgent config)) : paramsHeaders params
reqQuery = let query = paramsQuery params
queryExtraUnreserved = configQueryExtraUnreserved config
in if B.null queryExtraUnreserved
then NH.renderQuery True query
else NH.renderQueryPartialEscape True (toPartialEscapeQuery queryExtraUnreserved query)
pReq = parsedReq { NH.method = rMethod req2
, NH.requestHeaders = reqHeaders
, NH.queryString = reqQuery
}
outReq <- case paramsBody (rParams req2) of
outReq <- case paramsBody params of
ParamBodyNone -> pure (pReq { NH.requestBody = mempty })
ParamBodyB bs -> pure (pReq { NH.requestBody = NH.RequestBodyBS bs })
ParamBodyBL bl -> pure (pReq { NH.requestBody = NH.RequestBodyLBS bl })
Expand Down
14 changes: 13 additions & 1 deletion kubernetes/lib/Kubernetes/OpenAPI/Core.hs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ import Data.Function ((&))
import Data.Foldable(foldlM)
import Data.Monoid ((<>))
import Data.Text (Text)
import Prelude (($), (.), (<$>), (<*>), Maybe(..), Bool(..), Char, String, fmap, mempty, pure, return, show, IO, Monad, Functor)
import Prelude (($), (.), (&&), (<$>), (<*>), Maybe(..), Bool(..), Char, String, fmap, mempty, pure, return, show, IO, Monad, Functor, maybe)

-- * KubernetesClientConfig

Expand All @@ -79,6 +79,7 @@ data KubernetesClientConfig = KubernetesClientConfig
, configLogContext :: LogContext -- ^ Configures the logger
, configAuthMethods :: [AnyAuthMethod] -- ^ List of configured auth methods
, configValidateAuthMethods :: Bool -- ^ throw exceptions if auth methods are not configured
, configQueryExtraUnreserved :: B.ByteString -- ^ Configures additional querystring characters which must not be URI encoded, e.g. '+' or ':'
}

-- | display the config
Expand Down Expand Up @@ -109,6 +110,7 @@ newConfig = do
, configLogContext = logCxt
, configAuthMethods = []
, configValidateAuthMethods = True
, configQueryExtraUnreserved = "+"
}

-- | updates config use AuthMethod on matching requests
Expand Down Expand Up @@ -336,6 +338,16 @@ toQuery :: WH.ToHttpApiData a => (BC.ByteString, Maybe a) -> [NH.QueryItem]
toQuery x = [(fmap . fmap) toQueryParam x]
where toQueryParam = T.encodeUtf8 . WH.toQueryParam

toPartialEscapeQuery :: B.ByteString -> NH.Query -> NH.PartialEscapeQuery
toPartialEscapeQuery extraUnreserved query = fmap (\(k, v) -> (k, maybe [] go v)) query
where go :: B.ByteString -> [NH.EscapeItem]
go v = v & B.groupBy (\a b -> a `B.notElem` extraUnreserved && b `B.notElem` extraUnreserved)
& fmap (\xs -> if B.null xs then NH.QN xs
else if B.head xs `B.elem` extraUnreserved
then NH.QN xs -- Not Encoded
else NH.QE xs -- Encoded
)

-- *** OpenAPI `CollectionFormat` Utils

-- | Determines the format of the array if type array is used.
Expand Down
38 changes: 38 additions & 0 deletions kubernetes/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68576,6 +68576,17 @@ paths:
required: true
schema:
type: string
- description: allowWatchBookmarks requests watch events with type "BOOKMARK".
Servers that do not implement bookmarks may ignore this flag and bookmarks
are sent at the server's discretion. Clients should not assume bookmarks
are returned at any specific interval, nor may they assume the server will
send any BOOKMARK event during a session. If this is not a watch, this field
is ignored. If the feature gate WatchBookmarks is not enabled in apiserver,
this field is ignored.
in: query
name: allowWatchBookmarks
schema:
type: boolean
- description: |-
The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the "next key".

Expand Down Expand Up @@ -68614,6 +68625,14 @@ paths:
name: resourceVersion
schema:
type: string
- description: |-
resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.

Defaults to unset
in: query
name: resourceVersionMatch
schema:
type: string
- description: Timeout for the list/watch call. This limits the duration of
the call, regardless of any activity or inactivity.
in: query
Expand Down Expand Up @@ -68819,6 +68838,17 @@ paths:
required: true
schema:
type: string
- description: allowWatchBookmarks requests watch events with type "BOOKMARK".
Servers that do not implement bookmarks may ignore this flag and bookmarks
are sent at the server's discretion. Clients should not assume bookmarks
are returned at any specific interval, nor may they assume the server will
send any BOOKMARK event during a session. If this is not a watch, this field
is ignored. If the feature gate WatchBookmarks is not enabled in apiserver,
this field is ignored.
in: query
name: allowWatchBookmarks
schema:
type: boolean
- description: |-
The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the "next key".

Expand Down Expand Up @@ -68857,6 +68887,14 @@ paths:
name: resourceVersion
schema:
type: string
- description: |-
resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.

Defaults to unset
in: query
name: resourceVersionMatch
schema:
type: string
- description: Timeout for the list/watch call. This limits the duration of
the call, regardless of any activity or inactivity.
in: query
Expand Down
2 changes: 1 addition & 1 deletion kubernetes/stack.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
resolver: lts-14.7
resolver: lts-18.6
build:
haddock-arguments:
haddock-args:
Expand Down
Loading

0 comments on commit 541fb76

Please sign in to comment.