agent.go 21 KB

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