client.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  1. /*
  2. Copyright 2016 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package kube
  14. import (
  15. "bytes"
  16. "crypto/tls"
  17. "crypto/x509"
  18. "encoding/base64"
  19. "encoding/json"
  20. "errors"
  21. "flag"
  22. "fmt"
  23. "io"
  24. "io/ioutil"
  25. "net/http"
  26. "strconv"
  27. "strings"
  28. "time"
  29. "github.com/ghodss/yaml"
  30. "github.com/sirupsen/logrus"
  31. "k8s.io/api/core/v1"
  32. "k8s.io/apimachinery/pkg/util/sets"
  33. )
  34. var InClusterBaseURL string
  35. func init() {
  36. flag.StringVar(&InClusterBaseURL, "in-cluster-base-url", "https://kubernetes.default", "the base url to request k8s apiserver in cluster")
  37. }
  38. const (
  39. // TestContainerName specifies the primary container name.
  40. TestContainerName = "test"
  41. https = "https"
  42. maxRetries = 8
  43. retryDelay = 2 * time.Second
  44. requestTimeout = time.Minute
  45. // EmptySelector selects everything
  46. EmptySelector = ""
  47. // DefaultClusterAlias specifies the default cluster key to schedule jobs.
  48. DefaultClusterAlias = "default"
  49. )
  50. // newClient is used to allow mocking out the behavior of 'NewClient' while testing.
  51. var newClient = NewClient
  52. // Logger can print debug messages
  53. type Logger interface {
  54. Debugf(s string, v ...interface{})
  55. }
  56. // Client interacts with the Kubernetes api-server.
  57. type Client struct {
  58. // If logger is non-nil, log all method calls with it.
  59. logger Logger
  60. baseURL string
  61. deckURL string
  62. client *http.Client
  63. token string
  64. namespace string
  65. fake bool
  66. hiddenReposProvider func() []string
  67. hiddenOnly bool
  68. }
  69. // SetHiddenReposProvider takes a continuation that fetches a list of orgs and repos for
  70. // which PJs should not be returned.
  71. // NOTE: This function is not thread safe and should be called before the client is in use.
  72. func (c *Client) SetHiddenReposProvider(p func() []string, hiddenOnly bool) {
  73. c.hiddenReposProvider = p
  74. c.hiddenOnly = hiddenOnly
  75. }
  76. // Namespace returns a copy of the client pointing at the specified namespace.
  77. func (c *Client) Namespace(ns string) *Client {
  78. nc := *c
  79. nc.namespace = ns
  80. return &nc
  81. }
  82. func (c *Client) log(methodName string, args ...interface{}) {
  83. if c.logger == nil {
  84. return
  85. }
  86. var as []string
  87. for _, arg := range args {
  88. as = append(as, fmt.Sprintf("%v", arg))
  89. }
  90. c.logger.Debugf("%s(%s)", methodName, strings.Join(as, ", "))
  91. }
  92. // ConflictError is http 409.
  93. type ConflictError struct {
  94. e error
  95. }
  96. func (e ConflictError) Error() string {
  97. return e.e.Error()
  98. }
  99. // NewConflictError returns an error with the embedded inner error
  100. func NewConflictError(e error) ConflictError {
  101. return ConflictError{e: e}
  102. }
  103. // UnprocessableEntityError happens when the apiserver returns http 422.
  104. type UnprocessableEntityError struct {
  105. e error
  106. }
  107. func (e UnprocessableEntityError) Error() string {
  108. return e.e.Error()
  109. }
  110. // NewUnprocessableEntityError returns an error with the embedded inner error
  111. func NewUnprocessableEntityError(e error) UnprocessableEntityError {
  112. return UnprocessableEntityError{e: e}
  113. }
  114. // NotFoundError happens when the apiserver returns http 404
  115. type NotFoundError struct {
  116. e error
  117. }
  118. func (e NotFoundError) Error() string {
  119. return e.e.Error()
  120. }
  121. // NewNotFoundError returns an error with the embedded inner error
  122. func NewNotFoundError(e error) NotFoundError {
  123. return NotFoundError{e: e}
  124. }
  125. type request struct {
  126. method string
  127. path string
  128. deckPath string
  129. query map[string]string
  130. requestBody interface{}
  131. }
  132. func (c *Client) request(r *request, ret interface{}) error {
  133. out, err := c.requestRetry(r)
  134. if err != nil {
  135. return err
  136. }
  137. if ret != nil {
  138. if err := json.Unmarshal(out, ret); err != nil {
  139. return err
  140. }
  141. }
  142. return nil
  143. }
  144. func (c *Client) retry(r *request) (*http.Response, error) {
  145. var resp *http.Response
  146. var err error
  147. backoff := retryDelay
  148. for retries := 0; retries < maxRetries; retries++ {
  149. resp, err = c.doRequest(r.method, r.deckPath, r.path, r.query, r.requestBody)
  150. if err == nil {
  151. if resp.StatusCode < 500 {
  152. break
  153. }
  154. resp.Body.Close()
  155. }
  156. time.Sleep(backoff)
  157. backoff *= 2
  158. }
  159. return resp, err
  160. }
  161. // Retry on transport failures. Does not retry on 500s.
  162. func (c *Client) requestRetryStream(r *request) (io.ReadCloser, error) {
  163. if c.fake && r.deckPath == "" {
  164. return nil, nil
  165. }
  166. resp, err := c.retry(r)
  167. if err != nil {
  168. return nil, err
  169. }
  170. if resp.StatusCode == 409 {
  171. return nil, NewConflictError(fmt.Errorf("body cannot be streamed"))
  172. } else if resp.StatusCode < 200 || resp.StatusCode > 299 {
  173. return nil, fmt.Errorf("response has status \"%s\"", resp.Status)
  174. }
  175. return resp.Body, nil
  176. }
  177. // Retry on transport failures. Does not retry on 500s.
  178. func (c *Client) requestRetry(r *request) ([]byte, error) {
  179. if c.fake && r.deckPath == "" {
  180. return []byte("{}"), nil
  181. }
  182. resp, err := c.retry(r)
  183. if err != nil {
  184. return nil, err
  185. }
  186. defer resp.Body.Close()
  187. rb, err := ioutil.ReadAll(resp.Body)
  188. if err != nil {
  189. return nil, err
  190. }
  191. if resp.StatusCode == 409 {
  192. return nil, NewConflictError(fmt.Errorf("body: %s", string(rb)))
  193. } else if resp.StatusCode == 422 {
  194. return nil, NewUnprocessableEntityError(fmt.Errorf("body: %s", string(rb)))
  195. } else if resp.StatusCode == 404 {
  196. return nil, NewNotFoundError(fmt.Errorf("body: %s", string(rb)))
  197. } else if resp.StatusCode < 200 || resp.StatusCode > 299 {
  198. return nil, fmt.Errorf("response has status \"%s\" and body \"%s\"", resp.Status, string(rb))
  199. }
  200. return rb, nil
  201. }
  202. func (c *Client) doRequest(method, deckPath, urlPath string, query map[string]string, body interface{}) (*http.Response, error) {
  203. url := c.baseURL + urlPath
  204. if c.deckURL != "" && deckPath != "" {
  205. url = c.deckURL + deckPath
  206. }
  207. var buf io.Reader
  208. if body != nil {
  209. b, err := json.Marshal(body)
  210. if err != nil {
  211. return nil, err
  212. }
  213. buf = bytes.NewBuffer(b)
  214. }
  215. req, err := http.NewRequest(method, url, buf)
  216. if err != nil {
  217. return nil, err
  218. }
  219. if c.token != "" {
  220. req.Header.Set("Authorization", "Bearer "+c.token)
  221. }
  222. if method == http.MethodPatch {
  223. req.Header.Set("Content-Type", "application/strategic-merge-patch+json")
  224. } else {
  225. req.Header.Set("Content-Type", "application/json")
  226. }
  227. q := req.URL.Query()
  228. for k, v := range query {
  229. q.Add(k, v)
  230. }
  231. req.URL.RawQuery = q.Encode()
  232. return c.client.Do(req)
  233. }
  234. // NewFakeClient creates a client that doesn't do anything. If you provide a
  235. // deck URL then the client will hit that for the supported calls.
  236. func NewFakeClient(deckURL string) *Client {
  237. return &Client{
  238. namespace: "default",
  239. deckURL: deckURL,
  240. client: &http.Client{},
  241. fake: true,
  242. }
  243. }
  244. // NewClientInCluster creates a Client that works from within a pod.
  245. func NewClientInCluster(namespace string) (*Client, error) {
  246. tokenFile := "/var/run/secrets/kubernetes.io/serviceaccount/token"
  247. token, err := ioutil.ReadFile(tokenFile)
  248. if err != nil {
  249. return nil, err
  250. }
  251. client := &http.Client{Timeout: requestTimeout}
  252. if strings.HasPrefix(InClusterBaseURL, https) {
  253. rootCAFile := "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
  254. certData, err := ioutil.ReadFile(rootCAFile)
  255. if err != nil {
  256. return nil, err
  257. }
  258. cp := x509.NewCertPool()
  259. cp.AppendCertsFromPEM(certData)
  260. client.Transport = &http.Transport{
  261. TLSClientConfig: &tls.Config{
  262. MinVersion: tls.VersionTLS12,
  263. RootCAs: cp,
  264. },
  265. }
  266. }
  267. return &Client{
  268. logger: logrus.WithField("client", "kube"),
  269. baseURL: InClusterBaseURL,
  270. client: client,
  271. token: string(token),
  272. namespace: namespace,
  273. }, nil
  274. }
  275. // Cluster represents the information necessary to talk to a Kubernetes
  276. // master endpoint.
  277. // NOTE: if your cluster runs on GKE you can use the following command to get these credentials:
  278. // gcloud --project <gcp_project> container clusters describe --zone <zone> <cluster_name>
  279. type Cluster struct {
  280. // The IP address of the cluster's master endpoint.
  281. Endpoint string `json:"endpoint"`
  282. // Base64-encoded public cert used by clients to authenticate to the
  283. // cluster endpoint.
  284. ClientCertificate string `json:"clientCertificate"`
  285. // Base64-encoded private key used by clients..
  286. ClientKey string `json:"clientKey"`
  287. // Base64-encoded public certificate that is the root of trust for the
  288. // cluster.
  289. ClusterCACertificate string `json:"clusterCaCertificate"`
  290. }
  291. // NewClientFromFile reads a Cluster object at clusterPath and returns an
  292. // authenticated client using the keys within.
  293. func NewClientFromFile(clusterPath, namespace string) (*Client, error) {
  294. data, err := ioutil.ReadFile(clusterPath)
  295. if err != nil {
  296. return nil, err
  297. }
  298. var c Cluster
  299. if err := yaml.Unmarshal(data, &c); err != nil {
  300. return nil, err
  301. }
  302. return NewClient(&c, namespace)
  303. }
  304. // UnmarshalClusterMap reads a map[string]Cluster in yaml bytes.
  305. func UnmarshalClusterMap(data []byte) (map[string]Cluster, error) {
  306. var raw map[string]Cluster
  307. if err := yaml.Unmarshal(data, &raw); err != nil {
  308. // If we failed to unmarshal the multicluster format try the single Cluster format.
  309. var singleConfig Cluster
  310. if err := yaml.Unmarshal(data, &singleConfig); err != nil {
  311. return nil, err
  312. }
  313. raw = map[string]Cluster{DefaultClusterAlias: singleConfig}
  314. }
  315. return raw, nil
  316. }
  317. // MarshalClusterMap writes c as yaml bytes.
  318. func MarshalClusterMap(c map[string]Cluster) ([]byte, error) {
  319. return yaml.Marshal(c)
  320. }
  321. // ClientMapFromFile reads the file at clustersPath and attempts to load a map of cluster aliases
  322. // to authenticated clients to the respective clusters.
  323. // The file at clustersPath is expected to be a yaml map from strings to Cluster structs OR it may
  324. // simply be a single Cluster struct which will be assigned the alias $DefaultClusterAlias.
  325. // If the file is an alias map, it must include the alias $DefaultClusterAlias.
  326. func ClientMapFromFile(clustersPath, namespace string) (map[string]*Client, error) {
  327. data, err := ioutil.ReadFile(clustersPath)
  328. if err != nil {
  329. return nil, fmt.Errorf("read error: %v", err)
  330. }
  331. raw, err := UnmarshalClusterMap(data)
  332. if err != nil {
  333. return nil, fmt.Errorf("unmarshal error: %v", err)
  334. }
  335. foundDefault := false
  336. result := map[string]*Client{}
  337. for alias, config := range raw {
  338. client, err := newClient(&config, namespace)
  339. if err != nil {
  340. return nil, fmt.Errorf("failed to load config for build cluster alias %q in file %q: %v", alias, clustersPath, err)
  341. }
  342. result[alias] = client
  343. if alias == DefaultClusterAlias {
  344. foundDefault = true
  345. }
  346. }
  347. if !foundDefault {
  348. return nil, fmt.Errorf("failed to find the required %q alias in build cluster config %q", DefaultClusterAlias, clustersPath)
  349. }
  350. return result, nil
  351. }
  352. // NewClient returns an authenticated Client using the keys in the Cluster.
  353. func NewClient(c *Cluster, namespace string) (*Client, error) {
  354. cc, err := base64.StdEncoding.DecodeString(c.ClientCertificate)
  355. if err != nil {
  356. return nil, err
  357. }
  358. ck, err := base64.StdEncoding.DecodeString(c.ClientKey)
  359. if err != nil {
  360. return nil, err
  361. }
  362. ca, err := base64.StdEncoding.DecodeString(c.ClusterCACertificate)
  363. if err != nil {
  364. return nil, err
  365. }
  366. cert, err := tls.X509KeyPair(cc, ck)
  367. if err != nil {
  368. return nil, err
  369. }
  370. cp := x509.NewCertPool()
  371. cp.AppendCertsFromPEM(ca)
  372. tr := &http.Transport{
  373. TLSClientConfig: &tls.Config{
  374. MinVersion: tls.VersionTLS12,
  375. Certificates: []tls.Certificate{cert},
  376. RootCAs: cp,
  377. },
  378. }
  379. return &Client{
  380. logger: logrus.WithField("client", "kube"),
  381. baseURL: c.Endpoint,
  382. client: &http.Client{Transport: tr, Timeout: requestTimeout},
  383. namespace: namespace,
  384. }, nil
  385. }
  386. // GetPod is analogous to kubectl get pods/NAME namespace=client.namespace
  387. func (c *Client) GetPod(name string) (Pod, error) {
  388. c.log("GetPod", name)
  389. var retPod Pod
  390. err := c.request(&request{
  391. path: fmt.Sprintf("/api/v1/namespaces/%s/pods/%s", c.namespace, name),
  392. }, &retPod)
  393. return retPod, err
  394. }
  395. // ListPods is analogous to kubectl get pods --selector=SELECTOR --namespace=client.namespace
  396. func (c *Client) ListPods(selector string) ([]Pod, error) {
  397. c.log("ListPods", selector)
  398. var pl struct {
  399. Items []Pod `json:"items"`
  400. }
  401. err := c.request(&request{
  402. path: fmt.Sprintf("/api/v1/namespaces/%s/pods", c.namespace),
  403. query: map[string]string{"labelSelector": selector},
  404. }, &pl)
  405. return pl.Items, err
  406. }
  407. // DeletePod deletes the pod at name in the client's default namespace.
  408. //
  409. // Analogous to kubectl delete pod
  410. func (c *Client) DeletePod(name string) error {
  411. c.log("DeletePod", name)
  412. return c.request(&request{
  413. method: http.MethodDelete,
  414. path: fmt.Sprintf("/api/v1/namespaces/%s/pods/%s", c.namespace, name),
  415. }, nil)
  416. }
  417. // CreateProwJob creates a prowjob in the client's default namespace.
  418. //
  419. // Analogous to kubectl create prowjob
  420. func (c *Client) CreateProwJob(j ProwJob) (ProwJob, error) {
  421. var representation string
  422. if out, err := json.Marshal(j); err == nil {
  423. representation = string(out[:])
  424. } else {
  425. representation = fmt.Sprintf("%v", j)
  426. }
  427. c.log("CreateProwJob", representation)
  428. var retJob ProwJob
  429. err := c.request(&request{
  430. method: http.MethodPost,
  431. path: fmt.Sprintf("/apis/prow.k8s.io/v1/namespaces/%s/prowjobs", c.namespace),
  432. requestBody: &j,
  433. }, &retJob)
  434. return retJob, err
  435. }
  436. func (c *Client) getHiddenRepos() sets.String {
  437. if c.hiddenReposProvider == nil {
  438. return nil
  439. }
  440. return sets.NewString(c.hiddenReposProvider()...)
  441. }
  442. func shouldHide(pj *ProwJob, hiddenRepos sets.String, showHiddenOnly bool) bool {
  443. if pj.Spec.Refs == nil {
  444. // periodic jobs do not have refs and therefore cannot be
  445. // hidden by the org/repo mechanism
  446. return false
  447. }
  448. shouldHide := hiddenRepos.HasAny(fmt.Sprintf("%s/%s", pj.Spec.Refs.Org, pj.Spec.Refs.Repo), pj.Spec.Refs.Org)
  449. if showHiddenOnly {
  450. return !shouldHide
  451. }
  452. return shouldHide
  453. }
  454. // GetProwJob returns the prowjob at name in the client's default namespace.
  455. //
  456. // Analogous to kubectl get prowjob/NAME
  457. func (c *Client) GetProwJob(name string) (ProwJob, error) {
  458. c.log("GetProwJob", name)
  459. var pj ProwJob
  460. err := c.request(&request{
  461. path: fmt.Sprintf("/apis/prow.k8s.io/v1/namespaces/%s/prowjobs/%s", c.namespace, name),
  462. }, &pj)
  463. if err == nil && shouldHide(&pj, c.getHiddenRepos(), c.hiddenOnly) {
  464. pj = ProwJob{}
  465. // Revealing the existence of this prow job is ok because the pj name cannot be used to
  466. // retrieve the pj itself. Furthermore, a timing attack could differentiate true 404s from
  467. // 404s returned when a hidden pj is queried so returning a 404 wouldn't hide the pj's existence.
  468. err = errors.New("403 ProwJob is hidden")
  469. }
  470. return pj, err
  471. }
  472. // ListProwJobs lists prowjobs using the specified labelSelector in the client's default namespace.
  473. //
  474. // Analogous to kubectl get prowjobs --selector=SELECTOR
  475. func (c *Client) ListProwJobs(selector string) ([]ProwJob, error) {
  476. c.log("ListProwJobs", selector)
  477. var jl struct {
  478. Items []ProwJob `json:"items"`
  479. }
  480. err := c.request(&request{
  481. path: fmt.Sprintf("/apis/prow.k8s.io/v1/namespaces/%s/prowjobs", c.namespace),
  482. deckPath: "/prowjobs.js",
  483. query: map[string]string{"labelSelector": selector},
  484. }, &jl)
  485. if err == nil {
  486. hidden := c.getHiddenRepos()
  487. var pjs []ProwJob
  488. for _, pj := range jl.Items {
  489. if !shouldHide(&pj, hidden, c.hiddenOnly) {
  490. pjs = append(pjs, pj)
  491. }
  492. }
  493. jl.Items = pjs
  494. }
  495. return jl.Items, err
  496. }
  497. // DeleteProwJob deletes the prowjob at name in the client's default namespace.
  498. func (c *Client) DeleteProwJob(name string) error {
  499. c.log("DeleteProwJob", name)
  500. return c.request(&request{
  501. method: http.MethodDelete,
  502. path: fmt.Sprintf("/apis/prow.k8s.io/v1/namespaces/%s/prowjobs/%s", c.namespace, name),
  503. }, nil)
  504. }
  505. // ReplaceProwJob will replace name with job in the client's default namespace.
  506. //
  507. // Analogous to kubectl replace prowjobs/NAME
  508. func (c *Client) ReplaceProwJob(name string, job ProwJob) (ProwJob, error) {
  509. c.log("ReplaceProwJob", name, job)
  510. var retJob ProwJob
  511. err := c.request(&request{
  512. method: http.MethodPut,
  513. path: fmt.Sprintf("/apis/prow.k8s.io/v1/namespaces/%s/prowjobs/%s", c.namespace, name),
  514. requestBody: &job,
  515. }, &retJob)
  516. return retJob, err
  517. }
  518. // CreatePod creates a pod in the client's default namespace.
  519. //
  520. // Analogous to kubectl create pod
  521. func (c *Client) CreatePod(p v1.Pod) (Pod, error) {
  522. c.log("CreatePod", p)
  523. var retPod Pod
  524. err := c.request(&request{
  525. method: http.MethodPost,
  526. path: fmt.Sprintf("/api/v1/namespaces/%s/pods", c.namespace),
  527. requestBody: &p,
  528. }, &retPod)
  529. return retPod, err
  530. }
  531. // GetLog returns the log of the default container in the specified pod, in the client's default namespace.
  532. //
  533. // Analogous to kubectl logs pod
  534. func (c *Client) GetLog(pod string) ([]byte, error) {
  535. c.log("GetLog", pod)
  536. return c.requestRetry(&request{
  537. path: fmt.Sprintf("/api/v1/namespaces/%s/pods/%s/log", c.namespace, pod),
  538. })
  539. }
  540. // GetLogTail returns the last n bytes of the log of the specified container in the specified pod,
  541. // in the client's default namespace.
  542. //
  543. // Analogous to kubectl logs pod --tail -1 --limit-bytes n -c container
  544. func (c *Client) GetLogTail(pod, container string, n int64) ([]byte, error) {
  545. c.log("GetLogTail", pod, n)
  546. return c.requestRetry(&request{
  547. path: fmt.Sprintf("/api/v1/namespaces/%s/pods/%s/log", c.namespace, pod),
  548. query: map[string]string{ // Because we want last n bytes, we fetch all lines and then limit to n bytes
  549. "tailLines": "-1",
  550. "container": container,
  551. "limitBytes": strconv.FormatInt(n, 10),
  552. },
  553. })
  554. }
  555. // GetContainerLog returns the log of a container in the specified pod, in the client's default namespace.
  556. //
  557. // Analogous to kubectl logs pod -c container
  558. func (c *Client) GetContainerLog(pod, container string) ([]byte, error) {
  559. c.log("GetContainerLog", pod)
  560. return c.requestRetry(&request{
  561. path: fmt.Sprintf("/api/v1/namespaces/%s/pods/%s/log", c.namespace, pod),
  562. query: map[string]string{"container": container},
  563. })
  564. }
  565. // CreateConfigMap creates a configmap.
  566. //
  567. // Analogous to kubectl create configmap
  568. func (c *Client) CreateConfigMap(content ConfigMap) (ConfigMap, error) {
  569. c.log("CreateConfigMap")
  570. var retConfigMap ConfigMap
  571. err := c.request(&request{
  572. method: http.MethodPost,
  573. path: fmt.Sprintf("/api/v1/namespaces/%s/configmaps", c.namespace),
  574. requestBody: &content,
  575. }, &retConfigMap)
  576. return retConfigMap, err
  577. }
  578. // GetConfigMap gets the configmap identified.
  579. func (c *Client) GetConfigMap(name, namespace string) (ConfigMap, error) {
  580. c.log("GetConfigMap", name)
  581. if namespace == "" {
  582. namespace = c.namespace
  583. }
  584. var retConfigMap ConfigMap
  585. err := c.request(&request{
  586. path: fmt.Sprintf("/api/v1/namespaces/%s/configmaps/%s", namespace, name),
  587. }, &retConfigMap)
  588. return retConfigMap, err
  589. }
  590. // ReplaceConfigMap puts the configmap into name.
  591. //
  592. // Analogous to kubectl replace configmap
  593. //
  594. // If config.Namespace is empty, the client's default namespace is used.
  595. // Returns the content returned by the apiserver
  596. func (c *Client) ReplaceConfigMap(name string, config ConfigMap) (ConfigMap, error) {
  597. c.log("ReplaceConfigMap", name)
  598. namespace := c.namespace
  599. if config.Namespace != "" {
  600. namespace = config.Namespace
  601. }
  602. var retConfigMap ConfigMap
  603. err := c.request(&request{
  604. method: http.MethodPut,
  605. path: fmt.Sprintf("/api/v1/namespaces/%s/configmaps/%s", namespace, name),
  606. requestBody: &config,
  607. }, &retConfigMap)
  608. return retConfigMap, err
  609. }