RESTClient 詳解
RESTClient 是 Kubernetes API 請求的一個(gè)基礎(chǔ)客戶端檩淋,它封裝了最基礎(chǔ)的請求操作懂拾。
接口與結(jié)構(gòu)體
結(jié)構(gòu)體
RESTClient struct 將一些常規(guī)的資源與其進(jìn)行了綁定。
type RESTClient struct {
//base 提供了一個(gè)最基礎(chǔ)的客戶端的URL夯秃,它通常是從我們傳入的配置文件中獲取,指向配置文件所在的集群。
base *url.URL
//versionedAPIPath 指的則是我們的APIPrefix GroupVersion, exp : apis/apps/v1
versionedAPIPath string
//content 則是提供了客戶端內(nèi)容的配置课梳,其中包含解碼和編碼器距辆,用于為傳輸?shù)臄?shù)據(jù)進(jìn)行解碼和編碼
content ClientContentConfig
//createBackoffMgr 提供了一個(gè)用于創(chuàng)建BackoffManager的方法
createBackoffMgr func() BackoffManager
//rateLimiter 則是提供了一個(gè)限速器
rateLimiter flowcontrol.RateLimiter
warningHandler WarningHandler
//http客戶端
Client *http.Client
}
在ClientContentConfig中提供了解碼和編碼的配置。其結(jié)構(gòu)體如下:
type ClientContentConfig struct {
//客戶端可接受的內(nèi)容類型
AcceptContentTypes string
//ContentType 指定用于與服務(wù)器通信的內(nèi)容格式暮刃。如果沒有設(shè)置AcceptContentTypes挑格,將會使用這個(gè)值作為AcceptContentTypes。
//如果ContentType沒有設(shè)置沾歪,則默認(rèn)是 "application/json"漂彤。
ContentType string
//GroupVersion 提供了資源組和資源版本
GroupVersion schema.GroupVersion
//Negotiator 提供了解碼和編碼器
Negotiator runtime.ClientNegotiator
}
接口
RESTClient Interface 定義了與K8S REST API 交互的一般操作。包常規(guī)的CRUD操作和獲取限速器以及資源版本灾搏。
type Interface interface {
GetRateLimiter() flowcontrol.RateLimiter
Verb(verb string) *Request
Post() *Request
Put() *Request
Patch(pt types.PatchType) *Request
Get() *Request
Delete() *Request
APIVersion() schema.GroupVersion
}
接下來我們看看它的接口實(shí)現(xiàn)挫望,我們發(fā)現(xiàn)它的實(shí)現(xiàn)非常簡單,主要是基于 Request 來實(shí)現(xiàn)狂窑。
它們僅僅是去創(chuàng)建一個(gè) Request 然后設(shè)置一下 Verb媳板,所以要想,深入了解泉哈,我們需要去看看 Request 的實(shí)現(xiàn)蛉幸。
func (c *RESTClient) Verb(verb string) *Request {
return NewRequest(c).Verb(verb)
}
func (c *RESTClient) Post() *Request {
return c.Verb("POST")
}
func (c *RESTClient) Put() *Request {
return c.Verb("PUT")
}
func (c *RESTClient) Patch(pt types.PatchType) *Request {
return c.Verb("PATCH").SetHeader("Content-Type", string(pt))
}
func (c *RESTClient) Get() *Request {...}
func (c *RESTClient) Delete() *Request {...}
Request
結(jié)構(gòu)
通過閱讀Request結(jié)構(gòu)體,我們可以發(fā)現(xiàn)丛晦,在Request中奕纫,它存儲一個(gè)請求的各種信息,包含:
type Request struct {
//RESTClient rest 客戶端
c *RESTClient
warningHandler WarningHandler
rateLimiter flowcontrol.RateLimiter
backoff BackoffManager
timeout time.Duration
//請求動(dòng)作
verb string
//前綴
pathPrefix string
//子路徑
subpath string
//參數(shù)
params url.Values
//HTTP頭
headers http.Header
//資源命名空間
namespace string
namespaceSet bool
//資源類型
resource string
//資源名稱
resourceName string
//子資源
subresource string
err error
body io.Reader
retry WithRetry
}
創(chuàng)建
而在 NewRequest 方法中,我們則可以發(fā)現(xiàn)烫沙,它其實(shí)是將RESTClient中的信息填充到 Request 中匹层。
其他信息則是通過調(diào)用函數(shù)的方法傳遞進(jìn)來。
func NewRequest(c *RESTClient) *Request {
var backoff BackoffManager
if c.createBackoffMgr != nil {
backoff = c.createBackoffMgr()
}
if backoff == nil {
backoff = noBackoff
}
var pathPrefix string
if c.base != nil {
pathPrefix = path.Join("/", c.base.Path, c.versionedAPIPath)
} else {
pathPrefix = path.Join("/", c.versionedAPIPath)
}
var timeout time.Duration
if c.Client != nil {
timeout = c.Client.Timeout
}
r := &Request{
c: c,
rateLimiter: c.rateLimiter,
backoff: backoff,
timeout: timeout,
pathPrefix: pathPrefix,
retry: &withRetry{maxRetries: 10},
warningHandler: c.warningHandler,
}
switch {
case len(c.content.AcceptContentTypes) > 0:
r.SetHeader("Accept", c.content.AcceptContentTypes)
case len(c.content.ContentType) > 0:
r.SetHeader("Accept", c.content.ContentType+", */*")
}
return r
}
數(shù)據(jù)填充
// SubResource sets a sub-resource path which can be multiple segments after the resource
// name but before the suffix.
func (r *Request) SubResource(subresources ...string) *Request {
...
r.subresource = subresource
return r
}
// Name sets the name of a resource to access (<resource>/[ns/<namespace>/]<name>)
func (r *Request) Name(resourceName string) *Request {
...
r.resourceName = resourceName
return r
}
// Namespace applies the namespace scope to a request (<resource>/[ns/<namespace>/]<name>)
func (r *Request) Namespace(namespace string) *Request {
...
r.namespaceSet = true
r.namespace = namespace
return r
}
// Param creates a query parameter with the given string value.
func (r *Request) Param(paramName, s string) *Request {
...
return r.setParam(paramName, s)
}
func (r *Request) setParam(paramName, value string) *Request {
if r.params == nil {
r.params = make(url.Values)
}
r.params[paramName] = append(r.params[paramName], value)
return r
}
func (r *Request) SetHeader(key string, values ...string) *Request {
if r.headers == nil {
r.headers = http.Header{}
}
r.headers.Del(key)
for _, value := range values {
r.headers.Add(key, value)
}
return r
}
func (r *Request) Timeout(d time.Duration) *Request {
...
r.timeout = d
return r
}
...只展示了部分代碼
URL構(gòu)建
看完了如何為Request設(shè)置值锌蓄,那我們來看看 Request 是如何將這些參數(shù)最終構(gòu)建成用于向K8S服務(wù)請求的URL升筏!
從下面代碼,我們可以清晰的看出瘸爽,Request 構(gòu)建 URL 的方法非常簡單:
先是按照一定的順序您访,將我們開始設(shè)置的namespace,resource,resourceName,subpath 等信息拼接起來得到 URL 的 Path。
然后再將查詢參數(shù)添加進(jìn)去剪决。
// URL returns the current working URL.
func (r *Request) URL() *url.URL {
//=======拼接 URL.Path========
p := r.pathPrefix
if r.namespaceSet && len(r.namespace) > 0 {
p = path.Join(p, "namespaces", r.namespace)
}
if len(r.resource) != 0 {
p = path.Join(p, strings.ToLower(r.resource))
}
// Join trims trailing slashes, so preserve r.pathPrefix's trailing slash for backwards compatibility if nothing was changed
if len(r.resourceName) != 0 || len(r.subpath) != 0 || len(r.subresource) != 0 {
p = path.Join(p, r.resourceName, r.subresource, r.subpath)
}
finalURL := &url.URL{}
if r.c.base != nil {
*finalURL = *r.c.base
}
finalURL.Path = p
//=======構(gòu)建 URL.RawQuery========
query := url.Values{}
for key, values := range r.params {
for _, value := range values {
query.Add(key, value)
}
}
// timeout is handled specially here.
if r.timeout != 0 {
query.Set("timeout", r.timeout.String())
}
finalURL.RawQuery = query.Encode()
return finalURL
}
另外還有一個(gè) finalURLTemplate 函數(shù)也是用于構(gòu)建 URL 灵汪,它也是基于上述這個(gè)函數(shù)進(jìn)行的構(gòu)建,只是會比上述這個(gè)函數(shù)校驗(yàn)更加嚴(yán)格昼捍,更加符合 K8S 資源 API 路徑規(guī)范识虚。這里就不做詳解了。
發(fā)起請求
構(gòu)建完了URL路徑妒茬,接下我們就來看看如何發(fā)起一個(gè)資源請求担锤。
我們發(fā)起一個(gè)資源請求的方式通常有3種:
- Do:執(zhí)行某一個(gè)操作。即CRUD乍钻。
- Watch:監(jiān)聽或觀察一個(gè)數(shù)據(jù)的變動(dòng)肛循。
- Stream: 獲取數(shù)據(jù)流铭腕。
Do
通過一下代碼,我們可以看出多糠,Do 主要做了兩件事情累舷,一件事情是發(fā)起 request 請求,另一件事情是將響應(yīng)數(shù)據(jù)轉(zhuǎn)換為 Result.
func (r *Request) Do(ctx context.Context) Result {
var result Result
// 發(fā)起請求
err := r.request(ctx, func(req *http.Request, resp *http.Response) {
// 轉(zhuǎn)換響應(yīng)數(shù)據(jù)
result = r.transformResponse(resp, req)
})
if err != nil {
return Result{err: err}
}
return result
}
發(fā)起請求
先來看看發(fā)起請求時(shí)都做了啥夹孔。它的注釋是這樣說的:
request connects to the server and invokes the provided function when a server response is
received. It handles retry behavior and up front validation of requests. It will invoke
fn at most once. It will return an error if a problem occurred prior to connecting to the
server - the provided function is responsible for handling server errors.request 將會連接到服務(wù)器被盈,并在接收到服務(wù)器的響應(yīng)時(shí)調(diào)用提供的函數(shù)fn.但至多調(diào)用一次,
他會對請求進(jìn)行預(yù)處理和處理相關(guān)的重試行為.
如果在連接到服務(wù)器前發(fā)生錯(cuò)誤搭伤,將返回錯(cuò)誤只怎。
我們提供的函數(shù)也將負(fù)責(zé)處理服務(wù)器錯(cuò)誤。
func (r *Request) request(ctx context.Context, fn func(*http.Request, *http.Response)) error {
//=====記錄 request 請求的一些 metric 指標(biāo)=====
start := time.Now()
defer func() {
metrics.RequestLatency.Observe(ctx, r.verb, r.finalURLTemplate(), time.Since(start))
}()
if r.err != nil {
klog.V(4).Infof("Error in request: %v", r.err)
return r.err
}
/* 請求預(yù)驗(yàn)證怜俐,驗(yàn)證 namespace 為空的情況下身堡,1 。請求不不能為POST拍鲤。
2 請求為GET贴谎,PUT,DELETE 時(shí)季稳,資源名稱不能為空*/
if err := r.requestPreflightCheck(); err != nil {
return err
}
client := r.c.Client
if client == nil {
client = http.DefaultClient
}
// 為上下文ctx設(shè)置一些超時(shí)的限制
if err := r.tryThrottle(ctx); err != nil {
return err
}
if r.timeout > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, r.timeout)
defer cancel()
}
// Right now we make about ten retry attempts if we get a Retry-After response.
var retryAfter *RetryAfter
for {
//獲取一個(gè) http request 請求擅这。
req, err := r.newHTTPRequest(ctx)
if err != nil {
return err
}
r.backoff.Sleep(r.backoff.CalculateBackoff(r.URL()))
if retryAfter != nil {
// 使用限速器做重試處理
if err := r.tryThrottleWithInfo(ctx, retryAfter.Reason); err != nil {
return err
}
retryAfter = nil
}
//發(fā)起請求
resp, err := client.Do(req)
//更新metric
updateURLMetrics(ctx, r, resp, err)
if err != nil {
r.backoff.UpdateBackoff(r.URL(), err, 0)
} else {
r.backoff.UpdateBackoff(r.URL(), err, resp.StatusCode)
}
done := func() bool {
//讀取和關(guān)閉響應(yīng)body
defer readAndCloseResponseBody(resp)
//服務(wù)器響應(yīng)處理,如果響應(yīng)不為空绞幌,將會調(diào)用我們傳遞進(jìn)來的處理方法蕾哟。
f := func(req *http.Request, resp *http.Response) {
if resp == nil {
return
}
fn(req, resp)
}
//重試處理
var retry bool
retryAfter, retry = r.retry.NextRetry(req, resp, err, func(req *http.Request, err error) bool {
if r.verb != "GET" {
return false
}
if net.IsConnectionReset(err) || net.IsProbableEOF(err) {
return true
}
return false
})
if retry {
err := r.retry.BeforeNextRetry(ctx, r.backoff, retryAfter, req.URL.String(), r.body)
if err == nil {
return false
}
klog.V(4).Infof("Could not retry request - %v", err)
}
// 調(diào)用上面定義好的服務(wù)器響應(yīng)處理
f(req, resp)
return true
}()
if done {
return err
}
}
}
通過閱讀上面的代碼和注釋,我們可以發(fā)現(xiàn)莲蜘,在發(fā)起 request 請求后,它做了以下動(dòng)作:
- request 函數(shù)會先對請求做一些簡單的校驗(yàn)帘营。
- 為上下文ctx設(shè)置一些超時(shí)的限制票渠。
- 根據(jù)我們的開始構(gòu)建的 Request 結(jié)構(gòu)體數(shù)據(jù), 構(gòu)建一個(gè) http Request 請求芬迄。
- 使用限速器做重試處理问顷,處理發(fā)生錯(cuò)誤就退出并返回錯(cuò)誤。
- 執(zhí)行請求禀梳。
- 重試處理杜窄,
- 處理服務(wù)器響應(yīng)數(shù)據(jù),如果服務(wù)器正確響應(yīng)算途,則退出塞耕。響應(yīng)失敗則會回到第3步。
轉(zhuǎn)換響應(yīng)數(shù)據(jù)
轉(zhuǎn)換響應(yīng)數(shù)據(jù)主要是在服務(wù)器成功響應(yīng)時(shí)嘴瓤,使用我們開始提供的解碼器與服務(wù)器響應(yīng)數(shù)據(jù)共同構(gòu)建一個(gè)包含以下信息的結(jié)構(gòu)體:
type Result struct {
//響應(yīng)內(nèi)容
body []byte
warnings []net.WarningHeader
//內(nèi)容類型
contentType string
//錯(cuò)誤信息
err error
//http 狀態(tài)碼
statusCode int
//解碼器
decoder runtime.Decoder
}
同樣的 Result 也為我們提供兩個(gè)方法來獲取數(shù)據(jù),它們都在內(nèi)部調(diào)用解碼器扫外,將數(shù)據(jù)轉(zhuǎn)換為 k8s 的runtime.Object:
func (r Result) Get() (runtime.Object, error) {
if r.err != nil {
return nil, r.Error()
}
if r.decoder == nil {
return nil, fmt.Errorf("serializer for %s doesn't exist", r.contentType)
}
// decode, but if the result is Status return that as an error instead.
out, _, err := r.decoder.Decode(r.body, nil, nil)
if err != nil {
return nil, err
}
switch t := out.(type) {
case *metav1.Status:
if t.Status != metav1.StatusSuccess {
return nil, errors.FromObject(t)
}
}
return out, nil
}
func (r Result) Into(obj runtime.Object) error {
... 和Get相同
out, _, err := r.decoder.Decode(r.body, nil, obj)
if err != nil || out == obj {
return err
}
... 和Get相同
return nil
}
通過上面的代碼閱讀莉钙,我們知道了 Request 是如何去構(gòu)建并發(fā)送一個(gè)常規(guī)的CURD請求的,并請求完成后如何處理響應(yīng)數(shù)據(jù)的筛谚。
Watch
我們在使用k8s的過程中磁玉,我們往往會使用 Watch 這個(gè)函數(shù)來監(jiān)聽一個(gè)數(shù)據(jù)的變化,以便數(shù)據(jù)發(fā)生修改時(shí)能給及時(shí)感知并同步驾讲。
它是怎么實(shí)現(xiàn)的蚊伞,我們現(xiàn)在就可以來了解以下。
和Do相似吮铭,它會做一些重試設(shè)置厚柳,但它不會對請求進(jìn)行預(yù)校驗(yàn)和使用限速器。
func (r *Request) Watch(ctx context.Context) (watch.Interface, error) {
// We specifically don't want to rate limit watches, so we
// don't use r.rateLimiter here.
if r.err != nil {
return nil, r.err
}
client := r.c.Client
if client == nil {
client = http.DefaultClient
}
isErrRetryableFunc := func(request *http.Request, err error) bool {
if net.IsProbableEOF(err) || net.IsTimeout(err) {
return true
}
return false
}
var retryAfter *RetryAfter
url := r.URL().String()
for {
//獲取 HTTP Request
req, err := r.newHTTPRequest(ctx)
if err != nil {
return nil, err
}
r.backoff.Sleep(r.backoff.CalculateBackoff(r.URL()))
if retryAfter != nil {
if err := r.tryThrottleWithInfo(ctx, retryAfter.Reason); err != nil {
return nil, err
}
retryAfter = nil
}
//執(zhí)行 HTTP Request
resp, err := client.Do(req)
updateURLMetrics(ctx, r, resp, err)
if r.c.base != nil {
if err != nil {
r.backoff.UpdateBackoff(r.c.base, err, 0)
} else {
r.backoff.UpdateBackoff(r.c.base, err, resp.StatusCode)
}
}
// 如果請求成功 創(chuàng)建并返回一個(gè)數(shù)據(jù)流觀察者---StreamWatcher
if err == nil && resp.StatusCode == http.StatusOK {
return r.newStreamWatcher(resp)
}
// 請求失敗沐兵,重試處理
// 可以重試就繼續(xù)重試别垮,不可以重試就處理響應(yīng)數(shù)據(jù)并返回錯(cuò)誤。
done, transformErr := func() (bool, error) {
defer readAndCloseResponseBody(resp)
var retry bool
retryAfter, retry = r.retry.NextRetry(req, resp, err, isErrRetryableFunc)
if retry {
err := r.retry.BeforeNextRetry(ctx, r.backoff, retryAfter, url, r.body)
if err == nil {
return false, nil
}
klog.V(4).Infof("Could not retry request - %v", err)
}
if resp == nil {
// the server must have sent us an error in 'err'
return true, nil
}
if result := r.transformResponse(resp, req); result.err != nil {
return true, result.err
}
return true, fmt.Errorf("for request %s, got status: %v", url, resp.StatusCode)
}()
if done {
// 對于一些常見的網(wǎng)絡(luò)錯(cuò)誤扎谎,直接返回一個(gè)空的觀察者
if isErrRetryableFunc(req, err) {
return watch.NewEmptyWatch(), nil
}
if err == nil {
err = transformErr
}
return nil, err
}
}
}
接下來我們看看他是怎么創(chuàng)建數(shù)據(jù)流觀察者(StreamWatcher)的碳想。
- 獲取 contentType。
- 將 contentTyp 解析為 mediaType 和 params毁靶。
- 根據(jù) mediaType 和 params 從 Request 的流式 StreamDecoder 中獲取 objectDecoder胧奔,streamingSerializer,framer预吆。
- 獲取 frameReader龙填,watchEventDecoder。
- 調(diào)用 watch.NewStreamWatcher 創(chuàng)建 StreamWatcher拐叉。
func (r *Request) newStreamWatcher(resp *http.Response) (watch.Interface, error) {
contentType := resp.Header.Get("Content-Type")
mediaType, params, err := mime.ParseMediaType(contentType)
if err != nil {
klog.V(4).Infof("Unexpected content type from the server: %q: %v", contentType, err)
}
objectDecoder, streamingSerializer, framer, err := r.c.content.Negotiator.StreamDecoder(mediaType, params)
if err != nil {
return nil, err
}
handleWarnings(resp.Header, r.warningHandler)
frameReader := framer.NewFrameReader(resp.Body)
watchEventDecoder := streaming.NewDecoder(frameReader, streamingSerializer)
return watch.NewStreamWatcher(
restclientwatch.NewDecoder(watchEventDecoder, objectDecoder),
errors.NewClientErrorReporter(http.StatusInternalServerError, r.verb, "ClientWatchDecoding"),
), nil
}
然后我們通過查看NewStreamWatcher 會發(fā)現(xiàn)岩遗,這個(gè)在函數(shù)返回了一個(gè)StreamWatcher的同時(shí),開啟了一個(gè)協(xié)程進(jìn)行服務(wù)凤瘦。
在協(xié)程中不斷的通過資源解碼器獲取資源和事件類型宿礁,然后通過通道將數(shù)據(jù)傳輸出去。
func NewStreamWatcher(d Decoder, r Reporter) *StreamWatcher {
sw := &StreamWatcher{
source: d,
reporter: r,
result: make(chan Event),
done: make(chan struct{}),
}
go sw.receive()
return sw
}
// receive reads result from the decoder in a loop and sends down the result channel.
func (sw *StreamWatcher) receive() {
defer utilruntime.HandleCrash()
defer close(sw.result)
defer sw.Stop()
for {
action, obj, err := sw.source.Decode()
if err != nil {
switch err {
case io.EOF:
case io.ErrUnexpectedEOF:
klog.V(1).Infof("Unexpected EOF during watch stream event decoding: %v", err)
default:
if net.IsProbableEOF(err) || net.IsTimeout(err) {
klog.V(5).Infof("Unable to decode an event from the watch stream: %v", err)
} else {
select {
case <-sw.done:
case sw.result <- Event{
Type: Error,
Object: sw.reporter.AsObject(fmt.Errorf("unable to decode an event from the watch stream: %v", err)),
}:
}
}
}
return
}
select {
case <-sw.done:
return
case sw.result <- Event{
Type: action,
Object: obj,
}:
}
}
}
Stream
Stream 是用來獲取一個(gè)數(shù)據(jù)流蔬芥,它的做法相較于watch而言梆靖,則是比較簡單的,
僅僅只是在請求成功時(shí)返回 IO Reader笔诵。
func (r *Request) Stream(ctx context.Context) (io.ReadCloser, error) {
...
url := r.URL().String()
for {
req, err := r.newHTTPRequest(ctx)
...
resp, err := client.Do(req)
...
switch {
case (resp.StatusCode >= 200) && (resp.StatusCode < 300):
handleWarnings(resp.Header, r.warningHandler)
return resp.Body, nil
default:
...
}
}
}
經(jīng)過上面的介紹返吻,我們應(yīng)該大概解了RESTClient是怎么樣工作的了,但是為什么是k8s其他客戶端的基礎(chǔ)呢乎婿?我們可以通過一個(gè)例子看出來:
我們對比一下使用 RESTClient , ClientSet 以及 DynamicClient 來獲取pod测僵,大家應(yīng)該就理解了。
我們可以看到次酌,ClientSet恨课,DynamicClient 最終都是調(diào)用 RESTClient 里面的 方法來進(jìn)行請求舆乔。
//獲取配置
func getConfig() *rest.Config {
config, err := clientcmd.BuildConfigFromFlags("", "xxx")
if err != nil {
panic(err)
}
return config
}
RESTClient
func restClient() {
//config
config := getConfig()
config.GroupVersion = &v1.SchemeGroupVersion
config.NegotiatedSerializer = scheme.Codecs
config.APIPath = "/api"
//client
client, err := rest.RESTClientFor(config)
if err != nil {
panic(err)
}
// get data
var pod v1.Pod
req := client.Get().
Namespace("default").
Resource("pods").
Name("xxx").
Do(context.TODO()).
Into(&pod)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(pod.Name)
}
}
ClientSet
func clientCmd() {
config := getConfig()
clientSet, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err)
}
pod, err := clientSet.CoreV1().Pods("default").Get(context.TODO(), "xxx", v12.GetOptions{})
if err != nil {
fmt.Println(err)
} else {
fmt.Println(pod.Name)
}
}
func (c *pods) Get(ctx context.Context, name string, options metav1.GetOptions) (result *v1.Pod, err error) {
result = &v1.Pod{}
err = c.client.Get().
Namespace(c.ns).
Resource("pods").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do(ctx).
Into(result)
return
}
type pods struct {
client rest.Interface
ns string
}
// newPods returns a Pods
func newPods(c *CoreV1Client, namespace string) *pods {
return &pods{
client: c.RESTClient(),
ns: namespace,
}
}
DynamicClient
func dynamicCli() {
resource := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
config := getConfig()
cli, err := dynamic.NewForConfig(config)
if err != nil {
panic(err)
}
pod, err := cli.Resource(resource).Namespace("default").Get(context.TODO(), "xxx",v12.GetOptions{})
if err != nil {
fmt.Println(err)
} else {
fmt.Println(pod.GetName())
}
}
func (c *dynamicResourceClient) Get(ctx context.Context, name string, opts metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error) {
if len(name) == 0 {
return nil, fmt.Errorf("name is required")
}
result := c.client.client.Get().
AbsPath(append(c.makeURLSegments(name), subresources...)...).
SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
Do(ctx)
if err := result.Error(); err != nil {
return nil, err
}
retBytes, err := result.Raw()
if err != nil {
return nil, err
}
uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes)
if err != nil {
return nil, err
}
return uncastObj.(*unstructured.Unstructured), nil
}
type dynamicClient struct {
client *rest.RESTClient
}
type dynamicResourceClient struct {
client *dynamicClient
namespace string
resource schema.GroupVersionResource
}