agent.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788
  1. package kubernetes
  2. import (
  3. "bufio"
  4. "bytes"
  5. "context"
  6. "fmt"
  7. "io"
  8. "strings"
  9. "github.com/porter-dev/porter/internal/kubernetes/provisioner"
  10. "github.com/porter-dev/porter/internal/kubernetes/provisioner/aws"
  11. "github.com/porter-dev/porter/internal/kubernetes/provisioner/aws/ecr"
  12. "github.com/porter-dev/porter/internal/kubernetes/provisioner/aws/eks"
  13. "github.com/porter-dev/porter/internal/kubernetes/provisioner/do"
  14. "github.com/porter-dev/porter/internal/kubernetes/provisioner/do/docr"
  15. "github.com/porter-dev/porter/internal/kubernetes/provisioner/do/doks"
  16. "github.com/porter-dev/porter/internal/kubernetes/provisioner/gcp"
  17. "github.com/porter-dev/porter/internal/kubernetes/provisioner/gcp/gke"
  18. "github.com/porter-dev/porter/internal/models"
  19. "github.com/porter-dev/porter/internal/models/integrations"
  20. "github.com/porter-dev/porter/internal/oauth"
  21. "github.com/porter-dev/porter/internal/registry"
  22. "github.com/porter-dev/porter/internal/repository"
  23. "golang.org/x/oauth2"
  24. "github.com/gorilla/websocket"
  25. "github.com/porter-dev/porter/internal/helm/grapher"
  26. appsv1 "k8s.io/api/apps/v1"
  27. batchv1 "k8s.io/api/batch/v1"
  28. batchv1beta1 "k8s.io/api/batch/v1beta1"
  29. v1 "k8s.io/api/core/v1"
  30. v1beta1 "k8s.io/api/extensions/v1beta1"
  31. "k8s.io/apimachinery/pkg/api/errors"
  32. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  33. "k8s.io/cli-runtime/pkg/genericclioptions"
  34. "k8s.io/client-go/informers"
  35. "k8s.io/client-go/kubernetes"
  36. "k8s.io/client-go/tools/cache"
  37. "github.com/porter-dev/porter/internal/config"
  38. )
  39. // Agent is a Kubernetes agent for performing operations that interact with the
  40. // api server
  41. type Agent struct {
  42. RESTClientGetter genericclioptions.RESTClientGetter
  43. Clientset kubernetes.Interface
  44. }
  45. type Message struct {
  46. EventType string `json:"event_type"`
  47. Object interface{}
  48. Kind string
  49. }
  50. type ListOptions struct {
  51. FieldSelector string
  52. }
  53. // CreateConfigMap creates the configmap given the key-value pairs and namespace
  54. func (a *Agent) CreateConfigMap(name string, namespace string, configMap map[string]string) (*v1.ConfigMap, error) {
  55. return a.Clientset.CoreV1().ConfigMaps(namespace).Create(
  56. context.TODO(),
  57. &v1.ConfigMap{
  58. ObjectMeta: metav1.ObjectMeta{
  59. Name: name,
  60. Namespace: namespace,
  61. Labels: map[string]string{
  62. "porter": "true",
  63. },
  64. },
  65. Data: configMap,
  66. },
  67. metav1.CreateOptions{},
  68. )
  69. }
  70. // UpdateConfigMap updates the configmap given its name and namespace
  71. func (a *Agent) UpdateConfigMap(name string, namespace string, configMap map[string]string) (*v1.ConfigMap, error) {
  72. return a.Clientset.CoreV1().ConfigMaps(namespace).Update(
  73. context.TODO(),
  74. &v1.ConfigMap{
  75. ObjectMeta: metav1.ObjectMeta{
  76. Name: name,
  77. Namespace: namespace,
  78. Labels: map[string]string{
  79. "porter": "true",
  80. },
  81. },
  82. Data: configMap,
  83. },
  84. metav1.UpdateOptions{},
  85. )
  86. }
  87. // DeleteConfigMap deletes the configmap given its name and namespace
  88. func (a *Agent) DeleteConfigMap(name string, namespace string) (error) {
  89. return a.Clientset.CoreV1().ConfigMaps(namespace).Delete(
  90. context.TODO(),
  91. name,
  92. metav1.DeleteOptions{},
  93. )
  94. }
  95. // GetConfigMap retrieves the configmap given its name and namespace
  96. func (a *Agent) GetConfigMap(name string, namespace string) (*v1.ConfigMap, error) {
  97. return a.Clientset.CoreV1().ConfigMaps(namespace).Get(
  98. context.TODO(),
  99. name,
  100. metav1.GetOptions{},
  101. )
  102. }
  103. // ListConfigMaps simply lists namespaces
  104. func (a *Agent) ListConfigMaps(namespace string) (*v1.ConfigMapList, error) {
  105. return a.Clientset.CoreV1().ConfigMaps(namespace).List(
  106. context.TODO(),
  107. metav1.ListOptions{
  108. LabelSelector: "porter=true",
  109. },
  110. )
  111. }
  112. // ListNamespaces simply lists namespaces
  113. func (a *Agent) ListNamespaces() (*v1.NamespaceList, error) {
  114. return a.Clientset.CoreV1().Namespaces().List(
  115. context.TODO(),
  116. metav1.ListOptions{},
  117. )
  118. }
  119. // ListJobsByLabel lists jobs in a namespace matching a label
  120. type Label struct {
  121. Key string
  122. Val string
  123. }
  124. func (a *Agent) ListJobsByLabel(namespace string, labels ...Label) ([]batchv1.Job, error) {
  125. selectors := make([]string, 0)
  126. for _, label := range labels {
  127. selectors = append(selectors, fmt.Sprintf("%s=%s", label.Key, label.Val))
  128. }
  129. resp, err := a.Clientset.BatchV1().Jobs(namespace).List(
  130. context.TODO(),
  131. metav1.ListOptions{
  132. LabelSelector: strings.Join(selectors, ","),
  133. },
  134. )
  135. if err != nil {
  136. return nil, err
  137. }
  138. return resp.Items, nil
  139. }
  140. // GetJobPods lists all pods belonging to a job in a namespace
  141. func (a *Agent) GetJobPods(namespace, jobName string) ([]v1.Pod, error) {
  142. resp, err := a.Clientset.CoreV1().Pods(namespace).List(
  143. context.TODO(),
  144. metav1.ListOptions{
  145. LabelSelector: fmt.Sprintf("%s=%s", "job-name", jobName),
  146. },
  147. )
  148. if err != nil {
  149. return nil, err
  150. }
  151. return resp.Items, nil
  152. }
  153. // GetIngress gets ingress given the name and namespace
  154. func (a *Agent) GetIngress(namespace string, name string) (*v1beta1.Ingress, error) {
  155. return a.Clientset.ExtensionsV1beta1().Ingresses(namespace).Get(
  156. context.TODO(),
  157. name,
  158. metav1.GetOptions{},
  159. )
  160. }
  161. // GetDeployment gets the deployment given the name and namespace
  162. func (a *Agent) GetDeployment(c grapher.Object) (*appsv1.Deployment, error) {
  163. return a.Clientset.AppsV1().Deployments(c.Namespace).Get(
  164. context.TODO(),
  165. c.Name,
  166. metav1.GetOptions{},
  167. )
  168. }
  169. // GetStatefulSet gets the statefulset given the name and namespace
  170. func (a *Agent) GetStatefulSet(c grapher.Object) (*appsv1.StatefulSet, error) {
  171. return a.Clientset.AppsV1().StatefulSets(c.Namespace).Get(
  172. context.TODO(),
  173. c.Name,
  174. metav1.GetOptions{},
  175. )
  176. }
  177. // GetReplicaSet gets the replicaset given the name and namespace
  178. func (a *Agent) GetReplicaSet(c grapher.Object) (*appsv1.ReplicaSet, error) {
  179. return a.Clientset.AppsV1().ReplicaSets(c.Namespace).Get(
  180. context.TODO(),
  181. c.Name,
  182. metav1.GetOptions{},
  183. )
  184. }
  185. // GetDaemonSet gets the daemonset by name and namespace
  186. func (a *Agent) GetDaemonSet(c grapher.Object) (*appsv1.DaemonSet, error) {
  187. return a.Clientset.AppsV1().DaemonSets(c.Namespace).Get(
  188. context.TODO(),
  189. c.Name,
  190. metav1.GetOptions{},
  191. )
  192. }
  193. // GetJob gets the job by name and namespace
  194. func (a *Agent) GetJob(c grapher.Object) (*batchv1.Job, error) {
  195. return a.Clientset.BatchV1().Jobs(c.Namespace).Get(
  196. context.TODO(),
  197. c.Name,
  198. metav1.GetOptions{},
  199. )
  200. }
  201. // GetCronJob gets the CronJob by name and namespace
  202. func (a *Agent) GetCronJob(c grapher.Object) (*batchv1beta1.CronJob, error) {
  203. return a.Clientset.BatchV1beta1().CronJobs(c.Namespace).Get(
  204. context.TODO(),
  205. c.Name,
  206. metav1.GetOptions{},
  207. )
  208. }
  209. // GetPodsByLabel retrieves pods with matching labels
  210. func (a *Agent) GetPodsByLabel(selector string) (*v1.PodList, error) {
  211. // Search in all namespaces for matching pods
  212. return a.Clientset.CoreV1().Pods("").List(
  213. context.TODO(),
  214. metav1.ListOptions{
  215. LabelSelector: selector,
  216. },
  217. )
  218. }
  219. // DeletePod deletes a pod by name and namespace
  220. func (a *Agent) DeletePod(namespace string, name string) error {
  221. return a.Clientset.CoreV1().Pods(namespace).Delete(
  222. context.TODO(),
  223. name,
  224. metav1.DeleteOptions{},
  225. )
  226. }
  227. // GetPodLogs streams real-time logs from a given pod.
  228. func (a *Agent) GetPodLogs(namespace string, name string, conn *websocket.Conn) error {
  229. tails := int64(400)
  230. // follow logs
  231. podLogOpts := v1.PodLogOptions{
  232. Follow: true,
  233. TailLines: &tails,
  234. }
  235. req := a.Clientset.CoreV1().Pods(namespace).GetLogs(name, &podLogOpts)
  236. podLogs, err := req.Stream(context.TODO())
  237. if err != nil {
  238. return fmt.Errorf("Cannot open log stream for pod %s", name)
  239. }
  240. defer podLogs.Close()
  241. r := bufio.NewReader(podLogs)
  242. errorchan := make(chan error)
  243. go func() {
  244. // listens for websocket closing handshake
  245. for {
  246. if _, _, err := conn.ReadMessage(); err != nil {
  247. defer conn.Close()
  248. errorchan <- nil
  249. return
  250. }
  251. }
  252. }()
  253. go func() {
  254. for {
  255. select {
  256. case <-errorchan:
  257. defer close(errorchan)
  258. return
  259. default:
  260. }
  261. bytes, err := r.ReadBytes('\n')
  262. if writeErr := conn.WriteMessage(websocket.TextMessage, bytes); writeErr != nil {
  263. errorchan <- writeErr
  264. return
  265. }
  266. if err != nil {
  267. if err != io.EOF {
  268. errorchan <- err
  269. return
  270. }
  271. errorchan <- nil
  272. return
  273. }
  274. }
  275. }()
  276. for {
  277. select {
  278. case err = <-errorchan:
  279. return err
  280. }
  281. }
  282. }
  283. // StreamControllerStatus streams controller status. Supports Deployment, StatefulSet, ReplicaSet, and DaemonSet
  284. // TODO: Support Jobs
  285. func (a *Agent) StreamControllerStatus(conn *websocket.Conn, kind string) error {
  286. factory := informers.NewSharedInformerFactory(
  287. a.Clientset,
  288. 0,
  289. )
  290. var informer cache.SharedInformer
  291. // Spins up an informer depending on kind. Convert to lowercase for robustness
  292. switch strings.ToLower(kind) {
  293. case "deployment":
  294. informer = factory.Apps().V1().Deployments().Informer()
  295. case "statefulset":
  296. informer = factory.Apps().V1().StatefulSets().Informer()
  297. case "replicaset":
  298. informer = factory.Apps().V1().ReplicaSets().Informer()
  299. case "daemonset":
  300. informer = factory.Apps().V1().DaemonSets().Informer()
  301. case "job":
  302. informer = factory.Batch().V1().Jobs().Informer()
  303. }
  304. stopper := make(chan struct{})
  305. errorchan := make(chan error)
  306. defer close(errorchan)
  307. informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
  308. UpdateFunc: func(oldObj, newObj interface{}) {
  309. msg := Message{
  310. EventType: "UPDATE",
  311. Object: newObj,
  312. Kind: strings.ToLower(kind),
  313. }
  314. if writeErr := conn.WriteJSON(msg); writeErr != nil {
  315. errorchan <- writeErr
  316. return
  317. }
  318. },
  319. AddFunc: func(obj interface{}) {
  320. msg := Message{
  321. EventType: "ADD",
  322. Object: obj,
  323. Kind: strings.ToLower(kind),
  324. }
  325. if writeErr := conn.WriteJSON(msg); writeErr != nil {
  326. errorchan <- writeErr
  327. return
  328. }
  329. },
  330. DeleteFunc: func(obj interface{}) {
  331. msg := Message{
  332. EventType: "DELETE",
  333. Object: obj,
  334. Kind: strings.ToLower(kind),
  335. }
  336. if writeErr := conn.WriteJSON(msg); writeErr != nil {
  337. errorchan <- writeErr
  338. return
  339. }
  340. },
  341. })
  342. go func() {
  343. // listens for websocket closing handshake
  344. for {
  345. if _, _, err := conn.ReadMessage(); err != nil {
  346. defer conn.Close()
  347. defer close(stopper)
  348. errorchan <- nil
  349. return
  350. }
  351. }
  352. }()
  353. go informer.Run(stopper)
  354. for {
  355. select {
  356. case err := <-errorchan:
  357. return err
  358. }
  359. }
  360. }
  361. // ProvisionECR spawns a new provisioning pod that creates an ECR instance
  362. func (a *Agent) ProvisionECR(
  363. projectID uint,
  364. awsConf *integrations.AWSIntegration,
  365. ecrName string,
  366. repo repository.Repository,
  367. infra *models.Infra,
  368. operation provisioner.ProvisionerOperation,
  369. pgConf *config.DBConf,
  370. redisConf *config.RedisConf,
  371. provImageTag string,
  372. ) (*batchv1.Job, error) {
  373. id := infra.GetUniqueName()
  374. prov := &provisioner.Conf{
  375. ID: id,
  376. Name: fmt.Sprintf("prov-%s-%s", id, string(operation)),
  377. Kind: provisioner.ECR,
  378. Operation: operation,
  379. Redis: redisConf,
  380. Postgres: pgConf,
  381. ProvisionerImageTag: provImageTag,
  382. LastApplied: infra.LastApplied,
  383. AWS: &aws.Conf{
  384. AWSRegion: awsConf.AWSRegion,
  385. AWSAccessKeyID: string(awsConf.AWSAccessKeyID),
  386. AWSSecretAccessKey: string(awsConf.AWSSecretAccessKey),
  387. },
  388. ECR: &ecr.Conf{
  389. ECRName: ecrName,
  390. },
  391. }
  392. return a.provision(prov, infra, repo)
  393. }
  394. // ProvisionEKS spawns a new provisioning pod that creates an EKS instance
  395. func (a *Agent) ProvisionEKS(
  396. projectID uint,
  397. awsConf *integrations.AWSIntegration,
  398. eksName, machineType string,
  399. repo repository.Repository,
  400. infra *models.Infra,
  401. operation provisioner.ProvisionerOperation,
  402. pgConf *config.DBConf,
  403. redisConf *config.RedisConf,
  404. provImageTag string,
  405. ) (*batchv1.Job, error) {
  406. id := infra.GetUniqueName()
  407. prov := &provisioner.Conf{
  408. ID: id,
  409. Name: fmt.Sprintf("prov-%s-%s", id, string(operation)),
  410. Kind: provisioner.EKS,
  411. Operation: operation,
  412. Redis: redisConf,
  413. Postgres: pgConf,
  414. ProvisionerImageTag: provImageTag,
  415. LastApplied: infra.LastApplied,
  416. AWS: &aws.Conf{
  417. AWSRegion: awsConf.AWSRegion,
  418. AWSAccessKeyID: string(awsConf.AWSAccessKeyID),
  419. AWSSecretAccessKey: string(awsConf.AWSSecretAccessKey),
  420. },
  421. EKS: &eks.Conf{
  422. ClusterName: eksName,
  423. MachineType: machineType,
  424. },
  425. }
  426. return a.provision(prov, infra, repo)
  427. }
  428. // ProvisionGCR spawns a new provisioning pod that creates a GCR instance
  429. func (a *Agent) ProvisionGCR(
  430. projectID uint,
  431. gcpConf *integrations.GCPIntegration,
  432. repo repository.Repository,
  433. infra *models.Infra,
  434. operation provisioner.ProvisionerOperation,
  435. pgConf *config.DBConf,
  436. redisConf *config.RedisConf,
  437. provImageTag string,
  438. ) (*batchv1.Job, error) {
  439. id := infra.GetUniqueName()
  440. prov := &provisioner.Conf{
  441. ID: id,
  442. Name: fmt.Sprintf("prov-%s-%s", id, string(operation)),
  443. Kind: provisioner.GCR,
  444. Operation: operation,
  445. Redis: redisConf,
  446. Postgres: pgConf,
  447. ProvisionerImageTag: provImageTag,
  448. LastApplied: infra.LastApplied,
  449. GCP: &gcp.Conf{
  450. GCPRegion: gcpConf.GCPRegion,
  451. GCPProjectID: gcpConf.GCPProjectID,
  452. GCPKeyData: string(gcpConf.GCPKeyData),
  453. },
  454. }
  455. return a.provision(prov, infra, repo)
  456. }
  457. // ProvisionGKE spawns a new provisioning pod that creates a GKE instance
  458. func (a *Agent) ProvisionGKE(
  459. projectID uint,
  460. gcpConf *integrations.GCPIntegration,
  461. gkeName string,
  462. repo repository.Repository,
  463. infra *models.Infra,
  464. operation provisioner.ProvisionerOperation,
  465. pgConf *config.DBConf,
  466. redisConf *config.RedisConf,
  467. provImageTag string,
  468. ) (*batchv1.Job, error) {
  469. id := infra.GetUniqueName()
  470. prov := &provisioner.Conf{
  471. ID: id,
  472. Name: fmt.Sprintf("prov-%s-%s", id, string(operation)),
  473. Kind: provisioner.GKE,
  474. Operation: operation,
  475. Redis: redisConf,
  476. Postgres: pgConf,
  477. ProvisionerImageTag: provImageTag,
  478. LastApplied: infra.LastApplied,
  479. GCP: &gcp.Conf{
  480. GCPRegion: gcpConf.GCPRegion,
  481. GCPProjectID: gcpConf.GCPProjectID,
  482. GCPKeyData: string(gcpConf.GCPKeyData),
  483. },
  484. GKE: &gke.Conf{
  485. ClusterName: gkeName,
  486. },
  487. }
  488. return a.provision(prov, infra, repo)
  489. }
  490. // ProvisionDOCR spawns a new provisioning pod that creates a DOCR instance
  491. func (a *Agent) ProvisionDOCR(
  492. projectID uint,
  493. doConf *integrations.OAuthIntegration,
  494. doAuth *oauth2.Config,
  495. repo repository.Repository,
  496. docrName, docrSubscriptionTier string,
  497. infra *models.Infra,
  498. operation provisioner.ProvisionerOperation,
  499. pgConf *config.DBConf,
  500. redisConf *config.RedisConf,
  501. provImageTag string,
  502. ) (*batchv1.Job, error) {
  503. // get the token
  504. oauthInt, err := repo.OAuthIntegration.ReadOAuthIntegration(
  505. infra.DOIntegrationID,
  506. )
  507. if err != nil {
  508. return nil, err
  509. }
  510. tok, _, err := oauth.GetAccessToken(oauthInt, doAuth, repo)
  511. if err != nil {
  512. return nil, err
  513. }
  514. id := infra.GetUniqueName()
  515. prov := &provisioner.Conf{
  516. ID: id,
  517. Name: fmt.Sprintf("prov-%s-%s", id, string(operation)),
  518. Kind: provisioner.DOCR,
  519. Operation: operation,
  520. Redis: redisConf,
  521. Postgres: pgConf,
  522. ProvisionerImageTag: provImageTag,
  523. LastApplied: infra.LastApplied,
  524. DO: &do.Conf{
  525. DOToken: tok,
  526. },
  527. DOCR: &docr.Conf{
  528. DOCRName: docrName,
  529. DOCRSubscriptionTier: docrSubscriptionTier,
  530. },
  531. }
  532. return a.provision(prov, infra, repo)
  533. }
  534. // ProvisionDOKS spawns a new provisioning pod that creates a DOKS instance
  535. func (a *Agent) ProvisionDOKS(
  536. projectID uint,
  537. doConf *integrations.OAuthIntegration,
  538. doAuth *oauth2.Config,
  539. repo repository.Repository,
  540. doRegion, doksClusterName string,
  541. infra *models.Infra,
  542. operation provisioner.ProvisionerOperation,
  543. pgConf *config.DBConf,
  544. redisConf *config.RedisConf,
  545. provImageTag string,
  546. ) (*batchv1.Job, error) {
  547. // get the token
  548. oauthInt, err := repo.OAuthIntegration.ReadOAuthIntegration(
  549. infra.DOIntegrationID,
  550. )
  551. if err != nil {
  552. return nil, err
  553. }
  554. tok, _, err := oauth.GetAccessToken(oauthInt, doAuth, repo)
  555. if err != nil {
  556. return nil, err
  557. }
  558. id := infra.GetUniqueName()
  559. prov := &provisioner.Conf{
  560. ID: id,
  561. Name: fmt.Sprintf("prov-%s-%s", id, string(operation)),
  562. Kind: provisioner.DOKS,
  563. Operation: operation,
  564. Redis: redisConf,
  565. Postgres: pgConf,
  566. LastApplied: infra.LastApplied,
  567. ProvisionerImageTag: provImageTag,
  568. DO: &do.Conf{
  569. DOToken: tok,
  570. },
  571. DOKS: &doks.Conf{
  572. DORegion: doRegion,
  573. DOKSClusterName: doksClusterName,
  574. },
  575. }
  576. return a.provision(prov, infra, repo)
  577. }
  578. // ProvisionTest spawns a new provisioning pod that tests provisioning
  579. func (a *Agent) ProvisionTest(
  580. projectID uint,
  581. infra *models.Infra,
  582. repo repository.Repository,
  583. operation provisioner.ProvisionerOperation,
  584. pgConf *config.DBConf,
  585. redisConf *config.RedisConf,
  586. provImageTag string,
  587. ) (*batchv1.Job, error) {
  588. id := infra.GetUniqueName()
  589. prov := &provisioner.Conf{
  590. ID: id,
  591. Name: fmt.Sprintf("prov-%s-%s", id, string(operation)),
  592. Operation: operation,
  593. Kind: provisioner.Test,
  594. Redis: redisConf,
  595. Postgres: pgConf,
  596. ProvisionerImageTag: provImageTag,
  597. }
  598. return a.provision(prov, infra, repo)
  599. }
  600. func (a *Agent) provision(
  601. prov *provisioner.Conf,
  602. infra *models.Infra,
  603. repo repository.Repository,
  604. ) (*batchv1.Job, error) {
  605. prov.Namespace = "default"
  606. job, err := prov.GetProvisionerJobTemplate()
  607. if err != nil {
  608. return nil, err
  609. }
  610. job, err = a.Clientset.BatchV1().Jobs(prov.Namespace).Create(
  611. context.TODO(),
  612. job,
  613. metav1.CreateOptions{},
  614. )
  615. if err != nil {
  616. return nil, err
  617. }
  618. infra.LastApplied = prov.LastApplied
  619. infra, err = repo.Infra.UpdateInfra(infra)
  620. if err != nil {
  621. return nil, err
  622. }
  623. return job, nil
  624. }
  625. // CreateImagePullSecrets will create the required image pull secrets and
  626. // return a map from the registry name to the name of the secret.
  627. func (a *Agent) CreateImagePullSecrets(
  628. repo repository.Repository,
  629. namespace string,
  630. linkedRegs map[string]*models.Registry,
  631. doAuth *oauth2.Config,
  632. ) (map[string]string, error) {
  633. res := make(map[string]string)
  634. for key, val := range linkedRegs {
  635. _reg := registry.Registry(*val)
  636. data, err := _reg.GetDockerConfigJSON(repo, doAuth)
  637. if err != nil {
  638. return nil, err
  639. }
  640. secretName := fmt.Sprintf("porter-%s-%d", val.Externalize().Service, val.ID)
  641. secret, err := a.Clientset.CoreV1().Secrets(namespace).Get(
  642. context.TODO(),
  643. secretName,
  644. metav1.GetOptions{},
  645. )
  646. // if not found, create the secret
  647. if err != nil && errors.IsNotFound(err) {
  648. _, err = a.Clientset.CoreV1().Secrets(namespace).Create(
  649. context.TODO(),
  650. &v1.Secret{
  651. ObjectMeta: metav1.ObjectMeta{
  652. Name: secretName,
  653. },
  654. Data: map[string][]byte{
  655. string(v1.DockerConfigJsonKey): data,
  656. },
  657. Type: v1.SecretTypeDockerConfigJson,
  658. },
  659. metav1.CreateOptions{},
  660. )
  661. if err != nil {
  662. return nil, err
  663. }
  664. // add secret name to the map
  665. res[key] = secretName
  666. continue
  667. } else if err != nil {
  668. return nil, err
  669. }
  670. // otherwise, check that the secret contains the correct data: if
  671. // if doesn't, update it
  672. if !bytes.Equal(secret.Data[v1.DockerConfigJsonKey], data) {
  673. _, err := a.Clientset.CoreV1().Secrets(namespace).Update(
  674. context.TODO(),
  675. &v1.Secret{
  676. ObjectMeta: metav1.ObjectMeta{
  677. Name: secretName,
  678. },
  679. Data: map[string][]byte{
  680. string(v1.DockerConfigJsonKey): data,
  681. },
  682. Type: v1.SecretTypeDockerConfigJson,
  683. },
  684. metav1.UpdateOptions{},
  685. )
  686. if err != nil {
  687. return nil, err
  688. }
  689. }
  690. // add secret name to the map
  691. res[key] = secretName
  692. }
  693. return res, nil
  694. }